Compare commits
128 Commits
20250126
...
27afb2ea5f
| Author | SHA1 | Date | |
|---|---|---|---|
| 27afb2ea5f | |||
| 28016ada8d | |||
| 885a5b4c6d | |||
| 63149745ed | |||
| ef8b3d7668 | |||
| 8d2a0a28cc | |||
| 6ba9be41ec | |||
| 65fca14ac2 | |||
| 3818fd4625 | |||
| 3f88e5fe83 | |||
| 6a537642ab | |||
| b7f1a0abc9 | |||
| 33c976d3c9 | |||
| a17001cdf9 | |||
| 305c1d1f55 | |||
| 95808b094d | |||
| 2b56af27fe | |||
| 8edf746827 | |||
| 729a330e6c | |||
| 1ac5a4e42a | |||
| 83d212aa91 | |||
| bd24fba407 | |||
| 3448f62f31 | |||
| 567e66acb5 | |||
| 9e79133464 | |||
| e08107aa13 | |||
| 6eda2c4e48 | |||
| b16ae3a9ee | |||
| 56bba27ae3 | |||
| 1070dc32c1 | |||
| f61cf12f0a | |||
| 530b0c62c3 | |||
| 7e95440dd6 | |||
| 03492972cb | |||
| 9cca183677 | |||
| afd059b36b | |||
| 1b9f218acb | |||
| 32f91d7b2f | |||
| 69fbad34b6 | |||
| e026042604 | |||
| bb7e5182af | |||
| f0152e2496 | |||
| eccf928032 | |||
| 897306d1c3 | |||
| cbc779090f | |||
| d826744f26 | |||
| 3bdfb12d4f | |||
| c416ae9941 | |||
| abbba2890f | |||
| 9c6847d334 | |||
| af1c26a9fe | |||
| e0441a6494 | |||
| a88f05a9fe | |||
| b58559a18a | |||
| 2ac14a6c04 | |||
| b84256625c | |||
| 202a2b11b7 | |||
| 32f192ed7e | |||
| a0422683bd | |||
| ce67b35bfa | |||
| f016500f12 | |||
| 6069ebbbac | |||
| e587d84e05 | |||
| c01f2e328a | |||
| 8b498bbe73 | |||
| c6bc792ff7 | |||
| 1a307d82f9 | |||
| 8c5e2b2ebf | |||
| 03ad135ef0 | |||
| 2a78a9a41d | |||
| 1a214a169e | |||
| f8704c51d8 | |||
| 3feee0256c | |||
| 53472d807c | |||
| 9f06269aa4 | |||
| 78f242d808 | |||
| e6bab45a89 | |||
| 58286d52c5 | |||
| bac1963fa4 | |||
| 2415717ce0 | |||
| e7acbca01e | |||
| 7c4118b0b6 | |||
| 8178efdafd | |||
| 90026f931f | |||
| eee4613b25 | |||
| 24a9e96bbf | |||
| 5af5f4f068 | |||
| 85f9822b96 | |||
| 7fc6c64fd7 | |||
| 4488c2e6af | |||
| 0047607c1d | |||
| 45c368bbbb | |||
| c96cb86a29 | |||
| fe3e5f66be | |||
| 6fd17fbdf5 | |||
| f30dd2a520 | |||
| 21675d9479 | |||
| f669baa8a6 | |||
| c317012c99 | |||
| d0dbef20c7 | |||
| bd45900821 | |||
| 96a89bff92 | |||
| c05fc36ee3 | |||
| 05b0aa9a2f | |||
| 7b70b6cce5 | |||
| 5150ec5639 | |||
| 79529552d9 | |||
| c872c72bb5 | |||
| 58abf762c0 | |||
| e47c99fd21 | |||
| 24ddf1dc61 | |||
| 8f333191c3 | |||
| e4cb99657e | |||
| d644e8095d | |||
| 1796e128ad | |||
| 37fdac39dc | |||
| 13e1abf07e | |||
| 617abf7afe | |||
| d59ee7fddc | |||
| d545c6ca0f | |||
| 5249b277ec | |||
| e4b9fefa5a | |||
| d069ce3302 | |||
| 58c182adc0 | |||
| c36068aaf4 | |||
| fcb3aec81f | |||
| 7510dac82b | |||
| 55439be04c |
+46
-33
@@ -17,43 +17,50 @@ if(SANITIZE_ADDRESS_LEAK)
|
|||||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak")
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak")
|
||||||
endif(SANITIZE_ADDRESS_LEAK)
|
endif(SANITIZE_ADDRESS_LEAK)
|
||||||
|
|
||||||
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml REQUIRED)
|
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml Charts Svg REQUIRED)
|
||||||
find_library(GSL_LIB gsl REQUIRED)
|
|
||||||
find_library(GSLCBLAS_LIB gslcblas REQUIRED)
|
|
||||||
find_library(EXIF_LIB exif REQUIRED)
|
find_library(EXIF_LIB exif REQUIRED)
|
||||||
find_library(FITS_LIB cfitsio REQUIRED)
|
find_library(FITS_LIB cfitsio REQUIRED)
|
||||||
find_library(RAW_LIB NAMES raw_r REQUIRED)
|
find_library(RAW_LIB NAMES raw_r REQUIRED)
|
||||||
find_library(WCS_LIB wcs wcslib REQUIRED)
|
find_library(WCS_LIB wcs wcslib REQUIRED)
|
||||||
find_library(LCMS2_LIB lcms2 REQUIRED)
|
find_library(LCMS2_LIB lcms2 REQUIRED)
|
||||||
find_library(STELLARSOLVER_LIB stellarsolver)
|
find_library(STELLARSOLVER_LIB NAMES stellarsolver stellarsolver6)
|
||||||
|
|
||||||
add_subdirectory(libXISF)
|
add_subdirectory(libXISF)
|
||||||
|
|
||||||
set(TENMON_SRC
|
set(TENMON_SRC
|
||||||
about.cpp about.h
|
src/about.cpp src/about.h
|
||||||
batchprocessing.cpp batchprocessing.h batchprocessing.ui
|
src/batchprocessing.cpp src/batchprocessing.h src/batchprocessing.ui
|
||||||
database.cpp database.h
|
src/chartgraph.h src/chartgraph.cpp
|
||||||
databaseview.cpp databaseview.h
|
src/database.cpp src/database.h
|
||||||
delete.cpp
|
src/databasetree.cpp src/databasetree.h
|
||||||
filesystemwidget.cpp filesystemwidget.h
|
src/databasetreekeys.ui
|
||||||
histogram.cpp histogram.h
|
src/databaseview.cpp src/databaseview.h
|
||||||
httpdownloader.h httpdownloader.cpp
|
src/delete.cpp
|
||||||
imageinfo.cpp imageinfo.h
|
src/filemanager.h src/filemanager.cpp src/filemanager.ui
|
||||||
imageringlist.cpp imageringlist.h
|
src/filesystemwidget.cpp src/filesystemwidget.h
|
||||||
imagescrollarea.cpp imagescrollarea.h
|
src/fitskeyword.ui
|
||||||
imagewidget.h imagewidget.cpp
|
src/histogram.cpp src/histogram.h
|
||||||
loadrunable.cpp loadrunable.h
|
src/httpdownloader.h src/httpdownloader.cpp
|
||||||
main.cpp
|
src/imageinfo.cpp src/imageinfo.h
|
||||||
mainwindow.cpp mainwindow.h
|
src/imageinfodata.cpp src/imageinfodata.h
|
||||||
markedfiles.cpp markedfiles.h
|
src/imageringlist.cpp src/imageringlist.h
|
||||||
rawimage.cpp rawimage.h
|
src/imagescrollarea.cpp src/imagescrollarea.h
|
||||||
rawimage_sse.cpp
|
src/imagewidget.h src/imagewidget.cpp
|
||||||
scriptengine.cpp scriptengine.h
|
src/loadimage.h src/loadimage.cpp
|
||||||
settingsdialog.cpp settingsdialog.h
|
src/loadrunable.cpp src/loadrunable.h
|
||||||
starfit.cpp starfit.h
|
src/main.cpp
|
||||||
statusbar.cpp statusbar.h
|
src/mainwindow.cpp src/mainwindow.h
|
||||||
stfslider.cpp stfslider.h
|
src/markedfiles.cpp src/markedfiles.h
|
||||||
stretchtoolbar.cpp stretchtoolbar.h
|
src/mtfparam.h
|
||||||
|
src/rawimage.cpp src/rawimage.h
|
||||||
|
src/rawimage_sse.cpp
|
||||||
|
src/scriptengine.cpp src/scriptengine.h
|
||||||
|
src/settingsdialog.cpp src/settingsdialog.h
|
||||||
|
src/statusbar.cpp src/statusbar.h
|
||||||
|
src/stfslider.cpp src/stfslider.h
|
||||||
|
src/stretchtoolbar.cpp src/stretchtoolbar.h
|
||||||
|
src/tfloat16.h
|
||||||
|
thumbnailer/genthumbnail.cpp thumbnailer/genthumbnail.h
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_resources(TENMON_SRC resources/resources.qrc)
|
qt_add_resources(TENMON_SRC resources/resources.qrc)
|
||||||
@@ -74,7 +81,7 @@ endif()
|
|||||||
qt_add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC})
|
qt_add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC})
|
||||||
|
|
||||||
find_path(FITS_INCLUDE fitsio2.h PATH_SUFFIXES cfitsio REQUIRED)
|
find_path(FITS_INCLUDE fitsio2.h PATH_SUFFIXES cfitsio REQUIRED)
|
||||||
target_include_directories(tenmon PRIVATE ${FITS_INCLUDE} ${CMAKE_BINARY_DIR} ${libXISF_SOURCE_DIR})
|
target_include_directories(tenmon PRIVATE ${FITS_INCLUDE} ${CMAKE_BINARY_DIR} ${libXISF_SOURCE_DIR} "src")
|
||||||
|
|
||||||
option(COLOR_MANAGMENT "Enable sRGB framebuffer support for gamma correct images and color profiles support" ON)
|
option(COLOR_MANAGMENT "Enable sRGB framebuffer support for gamma correct images and color profiles support" ON)
|
||||||
if(COLOR_MANAGMENT)
|
if(COLOR_MANAGMENT)
|
||||||
@@ -85,20 +92,23 @@ find_path(STELLARSOLVER_INCLUDE stellarsolver.h PATH_SUFFIXES libstellarsolver)
|
|||||||
if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
|
if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
|
||||||
target_include_directories(tenmon PRIVATE ${STELLARSOLVER_INCLUDE})
|
target_include_directories(tenmon PRIVATE ${STELLARSOLVER_INCLUDE})
|
||||||
if(MXE)
|
if(MXE)
|
||||||
|
find_library(GSL_LIB gsl REQUIRED)
|
||||||
|
find_library(GSLCBLAS_LIB gslcblas REQUIRED)
|
||||||
|
target_compile_definitions(tenmon PRIVATE "stellarsolver_STATIC")
|
||||||
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB} ${GSL_LIB} ${GSLCBLAS_LIB} boost_regex-mt-x64)
|
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB} ${GSL_LIB} ${GSLCBLAS_LIB} boost_regex-mt-x64)
|
||||||
else(MXE)
|
else(MXE)
|
||||||
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB})
|
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB})
|
||||||
endif(MXE)
|
endif(MXE)
|
||||||
target_compile_definitions(tenmon PRIVATE "PLATESOLVER")
|
target_compile_definitions(tenmon PRIVATE "PLATESOLVER")
|
||||||
target_sources(tenmon PRIVATE
|
target_sources(tenmon PRIVATE
|
||||||
solver.cpp solver.h
|
src/solver.cpp src/solver.h
|
||||||
platesolving.cpp platesolving.h platesolving.ui
|
src/platesolving.cpp src/platesolving.h src/platesolving.ui
|
||||||
platesolvingsettings.cpp platesolvingsettings.h platesolvingsettings.ui
|
src/platesolvingsettings.cpp src/platesolvingsettings.h src/platesolvingsettings.ui
|
||||||
)
|
)
|
||||||
message(STATUS "Found stellarsolver ${STELLARSOLVER_INCLUDE} ${STELLARSOLVER_LIB}")
|
message(STATUS "Found stellarsolver ${STELLARSOLVER_INCLUDE} ${STELLARSOLVER_LIB}")
|
||||||
endif(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
|
endif(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
|
||||||
|
|
||||||
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
|
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml Qt6::Charts Qt6::Svg ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
|
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
|
||||||
elseif(UNIX)
|
elseif(UNIX)
|
||||||
@@ -125,6 +135,7 @@ if(UNIX AND NOT APPLE)
|
|||||||
install(FILES space.nouspiro.tenmon.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
|
install(FILES space.nouspiro.tenmon.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
|
||||||
install(FILES resources/space.nouspiro.tenmon.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps")
|
install(FILES resources/space.nouspiro.tenmon.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps")
|
||||||
install(FILES resources/space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
|
install(FILES resources/space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
|
||||||
|
install(FILES space.nouspiro.tenmon.xisf.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/mime/packages")
|
||||||
endif()
|
endif()
|
||||||
install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo")
|
install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo")
|
||||||
endif(UNIX AND NOT APPLE)
|
endif(UNIX AND NOT APPLE)
|
||||||
@@ -138,3 +149,5 @@ else()
|
|||||||
execute_process(COMMAND ${CMAKE_COMMAND} -Dlocal_dir=${CMAKE_CURRENT_SOURCE_DIR} -Doutput_dir=${CMAKE_CURRENT_BINARY_DIR}
|
execute_process(COMMAND ${CMAKE_COMMAND} -Dlocal_dir=${CMAKE_CURRENT_SOURCE_DIR} -Doutput_dir=${CMAKE_CURRENT_BINARY_DIR}
|
||||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/gitversion.cmake")
|
-P "${CMAKE_CURRENT_SOURCE_DIR}/gitversion.cmake")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(thumbnailer)
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ FITS/XISF image viewer with multithreaded image loading
|
|||||||
|
|
||||||
To get all dependencies install these packages
|
To get all dependencies install these packages
|
||||||
|
|
||||||
sudo apt install qt6-base-dev qt6-declarative-dev libqt6opengl6-dev libraw-dev libexif-dev libcfitsio-dev libgsl-dev wcslib-dev cmake libzstd-dev libqt6sql6-sqlite
|
sudo apt install qt6-base-dev qt6-declarative-dev qt6-charts-dev libqt6opengl6-dev libraw-dev libexif-dev libcfitsio-dev wcslib-dev cmake libzstd-dev libqt6sql6-sqlite
|
||||||
|
|
||||||
on OpenSUSE
|
on OpenSUSE
|
||||||
|
|
||||||
sudo zypper install gsl-devel libexif-devel libraw-devel wcslib-devel qt6-base-devel qt6-qml-devel libzstd-devel
|
sudo zypper install libexif-devel libraw-devel wcslib-devel qt6-base-devel qt6-qml-devel libzstd-devel
|
||||||
|
|
||||||
MacOS X
|
MacOS X
|
||||||
|
|
||||||
@@ -26,6 +26,12 @@ Then to build run standard cmake sequence
|
|||||||
cmake --build build
|
cmake --build build
|
||||||
./build/tenmon
|
./build/tenmon
|
||||||
|
|
||||||
|
To install it to system run this command as root
|
||||||
|
|
||||||
|
cmake --install build
|
||||||
|
|
||||||
For working plate solving you must have compiled and installed StellarSolver https://github.com/rlancaste/stellarsolver
|
For working plate solving you must have compiled and installed StellarSolver https://github.com/rlancaste/stellarsolver
|
||||||
It is important that you compile StellarSolver with Qt6. By default it use Qt5 but when linked with Qt6 program it will
|
It is important that you compile StellarSolver with Qt6. By default it use Qt5 but when linked with Qt6 program it will
|
||||||
crash.
|
crash.
|
||||||
|
|
||||||
|
Using OpenNGC database https://github.com/mattiaverga/OpenNGC under CC-BY-SA-4.0 https://creativecommons.org/licenses/by-sa/4.0/
|
||||||
|
|||||||
+133
-35
@@ -1,5 +1,7 @@
|
|||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<title>Help</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
h1, h2, h3, h4 { padding:0px; margin:10px; }
|
h1, h2, h3, h4 { padding:0px; margin:10px; }
|
||||||
p { padding:0px; margin:5px; }
|
p { padding:0px; margin:5px; }
|
||||||
@@ -9,14 +11,13 @@ img { margin: 5px; }
|
|||||||
<body>
|
<body>
|
||||||
<h2>Tenmon help</h2>
|
<h2>Tenmon help</h2>
|
||||||
|
|
||||||
<p>Tenmon is intended primarily for viewing astro photos and images. It supports the following formats:
|
<p>Tenmon is intended primarily for viewing astro photos and images. It supports the following formats:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>FITS 8, 16, 32 bit integer and 32, 64 bit float</li>
|
<li>FITS 8, 16, 32 bit integer and 32, 64 bit float</li>
|
||||||
<li>XISF 8, 16, 32 bit integer and 32, 64 bit float</li>
|
<li>XISF 8, 16, 32 bit integer and 32, 64 bit float</li>
|
||||||
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM and SVG images</li>
|
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM and SVG images</li>
|
||||||
<li>CR2, CR3, NEF, DNG raw images</li>
|
<li>CR2, CR3, NEF, DNG raw images</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Main windows</h3>
|
<h3>Main windows</h3>
|
||||||
<p>The main window shows the currently loaded image. On the left is the <i>Image info</i> panel which displays details about the loaded image.
|
<p>The main window shows the currently loaded image. On the left is the <i>Image info</i> panel which displays details about the loaded image.
|
||||||
@@ -35,26 +36,32 @@ In the case of saving JPEG or PNG, the stretch function is applied to the saved
|
|||||||
To open an image, you can also drag and drop it to main window.</p>
|
To open an image, you can also drag and drop it to main window.</p>
|
||||||
|
|
||||||
<h3>View</h3>
|
<h3>View</h3>
|
||||||
<p>The <i>View</i> menu has options to control the size and scale of displayed images:
|
<p>The <i>View</i> menu has options to control the size and scale of displayed images:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><i>Zoom In</i> and <i>Zoom Out</i> magnify and shrink the image. The mouse wheel can be also used to zoom freely.</li>
|
<li><i>Zoom In</i> and <i>Zoom Out</i> magnify and shrink the image. The mouse wheel can be also used to zoom freely.</li>
|
||||||
<li><i>Best fit</i> auto-zooms the image to fit the current size of the window.</li>
|
<li><i>Best fit</i> auto-zooms the image to fit the current size of the window.</li>
|
||||||
<li><i>100%</i> will zoom to 1:1 scale.</li>
|
<li><i>100%</i> will zoom to 1:1 scale.</li>
|
||||||
|
<li><i>Bayer mask</i> set which bayer mask should be used when doing demosaicing.</li>
|
||||||
|
<li><i>Colormap</i> select color pallette when showing image with false colours.</li>
|
||||||
<li><i>Fullscreen</i> enlarges the main window to the whole screen.</li>
|
<li><i>Fullscreen</i> enlarges the main window to the whole screen.</li>
|
||||||
<li><i>Thumbnails</i> will display small thumbnails for all images in the current directory.</li>
|
<li><i>Thumbnails</i> will display small thumbnails for all images in the current directory.</li>
|
||||||
|
<li><i>Slideshow</i> start showing all images periodically with interval that can be set in settings.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
<p>Colormap can also be user defined. Place image file named colormap.png into application data directory.
|
||||||
|
On Windows"C:/Users/<USER>/AppData/Roaming/nou/Tenmon" Linux: "~/.local/share/nou/Tenmon/" MacOS: "~/Library/Application Support/nou/Tenmon/"
|
||||||
|
This image should be 256 pixel wide. Each row of image will be used as separate color map and added to Colormap menu.</p>
|
||||||
|
|
||||||
|
|
||||||
<h3>Stretch toolbar</h3>
|
<h3>Stretch toolbar</h3>
|
||||||
<p>This panel changes how images are displayed.
|
<p>This panel changes how images are displayed.
|
||||||
<br><img src=":/about/stretch-panel.png"></p>
|
<br><img src=":/about/stretch-panel.png" alt="Stretch panel"></p>
|
||||||
<p>Starting on the left, there is slider scale with three adjustable points to manually control the stretch.
|
<p>Starting on the left, there is slider scale with three adjustable points to manually control the stretch.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>black point - all pixels with lower value (darker) than this setting will be clipped black</li>
|
<li>black point - all pixels with lower value (darker) than this setting will be clipped black</li>
|
||||||
<li>mid point - defines the value to be stretched to 50% intensity</li>
|
<li>mid point - defines the value to be stretched to 50% intensity</li>
|
||||||
<li>white point - all pixels with higher value (brighter) than this will be clipped white</li>
|
<li>white point - all pixels with higher value (brighter) than this will be clipped white</li>
|
||||||
</ul>
|
</ul>
|
||||||
Following the slider are 7 buttons for automatic stretching:
|
<p>Following the slider are 8 buttons to control image display:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><i>Linked channels</i> toggle stretching each RGB channel individually.</li>
|
<li><i>Linked channels</i> toggle stretching each RGB channel individually.</li>
|
||||||
<li><i>Auto Stretch</i> automatically apply black and mid points to render the image with optimal brightness.</li>
|
<li><i>Auto Stretch</i> automatically apply black and mid points to render the image with optimal brightness.</li>
|
||||||
@@ -62,7 +69,8 @@ Following the slider are 7 buttons for automatic stretching:
|
|||||||
<li><i>Invert</i> invert colors to display the image as negative.</li>
|
<li><i>Invert</i> invert colors to display the image as negative.</li>
|
||||||
<li><i>False colors</i> show black and white in false colour palette.</li>
|
<li><i>False colors</i> show black and white in false colour palette.</li>
|
||||||
<li><i>Debayer CFA</i> Demosaicing black and white image from CFA sensor to color one.</li>
|
<li><i>Debayer CFA</i> Demosaicing black and white image from CFA sensor to color one.</li>
|
||||||
<li><i>Apply Auto stretch on load</i> toggle automatically applying Autostretch for each image when loaded.</p>
|
<li><i>Apply Auto stretch on load</i> toggle automatically applying Autostretch for each image when loaded.</li>
|
||||||
|
<li><i>Draw equatorial grid</i> toggle drawing equatorial coordinate grid and catalogue objects. Needs that file have WCS data.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h3>Marking images</h3>
|
<h3>Marking images</h3>
|
||||||
@@ -77,13 +85,12 @@ mouse button and drag across thumbnails to mark them. Holding <i>Ctrl</i> will u
|
|||||||
<h3>File system and tree</h3>
|
<h3>File system and tree</h3>
|
||||||
<p>File system panel contain list of images in current opened directory. You can select file from this list and it will be displayed. It is also possible to
|
<p>File system panel contain list of images in current opened directory. You can select file from this list and it will be displayed. It is also possible to
|
||||||
use arrow keys to go back (left and up) and forth (right and down) between images.</p>
|
use arrow keys to go back (left and up) and forth (right and down) between images.</p>
|
||||||
<p>File tree show file system structure. You can right click to show context menu to perform various actions from <i>File</i> menu. There are also few others
|
<p>File tree show file system structure. You can right click to show context menu to perform various actions from <i>File</i> menu. There are also few others</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><i>Set as root directory</i> show only this directory and subdirectories</li>
|
<li><i>Set as root directory</i> show only this directory and subdirectories</li>
|
||||||
<li><i>Reset root directory</i> show whole file system</li>
|
<li><i>Reset root directory</i> show whole file system</li>
|
||||||
<li><i>Go up</i> show directory that is one level above current root directory</li>
|
<li><i>Go up</i> show directory that is one level above current root directory</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Database of FITS/XISF files</h3>
|
<h3>Database of FITS/XISF files</h3>
|
||||||
<p>Tenmon can scan a directory of FITS/XISF files and index metadata from FITS headers into it's internal database. This allows searching and sorting images based on that metadata.</p>
|
<p>Tenmon can scan a directory of FITS/XISF files and index metadata from FITS headers into it's internal database. This allows searching and sorting images based on that metadata.</p>
|
||||||
@@ -105,17 +112,20 @@ Setting both "RA pos" and "DEC pos" can return images that doesn't contain enter
|
|||||||
"RA range" and "DEC range" filter out images which center coordinate is within entered range.
|
"RA range" and "DEC range" filter out images which center coordinate is within entered range.
|
||||||
Pressing Enter or clicking on <i>Filter</i> button will filter out database record according to search parameter.
|
Pressing Enter or clicking on <i>Filter</i> button will filter out database record according to search parameter.
|
||||||
|
|
||||||
<p>Wildcards:
|
<p>Wildcards:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><b>%</b> (percent) is a wildcard representing zero or more of any characters.</li>
|
<li><b>%</b> (percent) is a wildcard representing zero or more of any characters.</li>
|
||||||
<li><b>_</b> (underscore) is a wildcard for exactly one of any character.</i>
|
<li><b>_</b> (underscore) is a wildcard for exactly one of any character.</li>
|
||||||
<li>Without wildcard characters, the exact string must match.</li>
|
<li>Without wildcard characters, the exact string must match.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
<p><img src=":/about/filter.png" alt="Filter"><br>
|
||||||
<br><img src=":/about/filter.png"><br>
|
|
||||||
This example filters for files where: "Bias" is in the file name, the OBJECT property is "M_42" (where the underscore can be any single character), and the DATE property begins with "2022".
|
This example filters for files where: "Bias" is in the file name, the OBJECT property is "M_42" (where the underscore can be any single character), and the DATE property begins with "2022".
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h3>Database tree</h3>
|
||||||
|
<p>This is another view that show indexed database as tree. You can add or remove tree filter that construct a tree structure from FITS keywords. Each level of tree
|
||||||
|
will be based on this filter. You can specify one keywords multiple times.</p>
|
||||||
|
|
||||||
<h3>Plate Solving</h3>
|
<h3>Plate Solving</h3>
|
||||||
<p>This module can plate solve images and update FITS header with solution for FITS and XISF images.
|
<p>This module can plate solve images and update FITS header with solution for FITS and XISF images.
|
||||||
<b>Profile</b> this set various parameters that affect star extraction and solving.
|
<b>Profile</b> this set various parameters that affect star extraction and solving.
|
||||||
@@ -129,17 +139,47 @@ solver.
|
|||||||
<p>Then finally there are various action button. Settings button show dialog where you can set path to existing index files or auto download some.
|
<p>Then finally there are various action button. Settings button show dialog where you can set path to existing index files or auto download some.
|
||||||
Extract button will just extract stars from image and it will show their count, HFR and eccentricity. This action doesn't need index files.
|
Extract button will just extract stars from image and it will show their count, HFR and eccentricity. This action doesn't need index files.
|
||||||
Solve button will try to find coordinates of images. Abort button will stop extraction or solving. Update FITS header will update FITS fits keywords
|
Solve button will try to find coordinates of images. Abort button will stop extraction or solving. Update FITS header will update FITS fits keywords
|
||||||
with found solution.
|
with found solution.</p>
|
||||||
|
|
||||||
<p>In settings dialog you can set path to index files which is by default custom internal one. It also try to locate commonly used path from other
|
<p>In settings dialog you can set path to index files which is by default custom internal one. It also try to locate commonly used path from other
|
||||||
programs like KStars for astrometry.net index files.
|
programs like KStars for astrometry.net index files.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>Batch processing</h3>
|
<h3>File Manager</h3>
|
||||||
|
<p>
|
||||||
|
This is simple double panel file manager. It can show columns with selected FITS keywords. Each panel have tabs where it easily switch between
|
||||||
|
multiple directories. You can copy or move files and directories either inside one panel or between two panels by selecting and then dragging.
|
||||||
|
By default files are copies. To move then press Shift key before start dragging. Double click on file will open it in main window if it is image
|
||||||
|
or other file it will open default program that is associated with it this file type. You can also drag file to other programs like from default
|
||||||
|
file explorer programs that are in system or from file explorer to this program.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In menu you can select which FITS keywords will be showed. Temporarilly disable loading FITS header or copy file paths of selected files as text.
|
||||||
|
</p>
|
||||||
|
|
||||||
This module allow to write scripts in JavaScript that process image files. Batch Processing window consist from three main parts. On top is list of input files and directories.
|
<h3>Command line options</h3>
|
||||||
|
<p>
|
||||||
|
Tenmon can be executed from command line. It support these command line options.
|
||||||
|
<ul>
|
||||||
|
<li><i>--thumb, --thumbnail <path></i> Generate thumbnail and save it to path. It generate it from first file provided as argument.</li>
|
||||||
|
<li><i>-s, --size <size></i> size of generated thumbnail. Aspect ratio of input image is preserved.</li>
|
||||||
|
<li><i>--script <script></i> execute a script from file path same manner as executed from GUI.</li>
|
||||||
|
<li><i>--scriptarg <arg>>;</i> pass this string as variable scriptarg to a running script.</li>
|
||||||
|
<li><i>--outdir <dir></i> output dir for script execution. By default current working directory is used.</li>
|
||||||
|
<li><i>--noexit</i> by default application exit when execution of script specified with --script ends. This prefent that.</li>
|
||||||
|
<li><i>-h, --help</i> show help end exit.</li>
|
||||||
|
Any other arguments are taken as input paths. If only one file path is specified then that image is open and image list is populated by directory
|
||||||
|
containing that image. If directory is specified then it is same as selecting that directory in "Open directory recusivelly". If multiple files are
|
||||||
|
specified then image list will contain just these speicified images.
|
||||||
|
|
||||||
|
When exuecting script with --script then these paths are used as input files and directories as in "Batch processing"
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Batch processing</h3>
|
||||||
|
<p>This module allow to write scripts in JavaScript that process image files. Batch Processing window consist from three main parts. On top is list of input files and directories.
|
||||||
You can add directories or individual files to this list. Directories are scanned recursively to find all files even non image files. This list of files is then passed to script in array named <b>files</b>.
|
You can add directories or individual files to this list. Directories are scanned recursively to find all files even non image files. This list of files is then passed to script in array named <b>files</b>.
|
||||||
In script you can then iterate through files like this.
|
In script you can then iterate through files like this.</p>
|
||||||
<pre>for(file of files)
|
<pre>for(file of files)
|
||||||
{
|
{
|
||||||
if(file.suffix() == "fits")
|
if(file.suffix() == "fits")
|
||||||
@@ -153,12 +193,13 @@ In script you can then iterate through files like this.
|
|||||||
this output directory is ignored.</p>
|
this output directory is ignored.</p>
|
||||||
|
|
||||||
<p>Bellow that is list of scripts. These scripts are located in application data directory. Select script which you want to run by clicking on it. Clicking on <i>Open scripts</i> will open directory with these scripts where you create new and edit them.
|
<p>Bellow that is list of scripts. These scripts are located in application data directory. Select script which you want to run by clicking on it. Clicking on <i>Open scripts</i> will open directory with these scripts where you create new and edit them.
|
||||||
Location of this directory is on Windows: "C:/Users/<USER>/AppData/Roaming/nou/Tenmon" Linux: "~/.local/share/nou/Tenmon/scripts" MacOS: "~/Library/Application Support/nou/Tenmon/scripts"</p>
|
Location of this directory is on Windows: "C:/Users/<USER>/AppData/Roaming/nou/Tenmon/scripts" Linux: "~/.local/share/nou/Tenmon/scripts" MacOS: "~/Library/Application Support/nou/Tenmon/scripts"</p>
|
||||||
|
|
||||||
<p>Next is Log windows that contain any messages that come from scripts. Mainly calls to <code>core.log()</code> At bottom there buttons that can start or stop execution of selected scripts.</p>
|
<p>Next is Log windows that contain any messages that come from scripts. Mainly calls to <code>core.log()</code> At bottom there is console that enable run simple script commands and
|
||||||
|
buttons that can start or stop execution of selected scripts.</p>
|
||||||
|
|
||||||
<h4>core</h4>
|
<h4>core</h4>
|
||||||
There is global object called <b>core</b> that have these methods.
|
<p>There is global object called <b>core</b> that have these methods.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><b>log(message)</b> print message to log window.</li>
|
<li><b>log(message)</b> print message to log window.</li>
|
||||||
<li><b>mark(file)</b> mark file same way as in GUI. Takes object of type <i>File</i> as argument.</li>
|
<li><b>mark(file)</b> mark file same way as in GUI. Takes object of type <i>File</i> as argument.</li>
|
||||||
@@ -175,10 +216,57 @@ There is global object called <b>core</b> that have these methods.
|
|||||||
<li><b>getItem(items)</b> show selection dialog which allow to select one item from array of items. It return selected item as string. When cancel is pressed it return Undefined.</li>
|
<li><b>getItem(items)</b> show selection dialog which allow to select one item from array of items. It return selected item as string. When cancel is pressed it return Undefined.</li>
|
||||||
<li><b>setStartingSolution(solution)</b> with this you can set starting point and image scale. It accepth object with attributes "ra", "dec", "pixscale".
|
<li><b>setStartingSolution(solution)</b> with this you can set starting point and image scale. It accepth object with attributes "ra", "dec", "pixscale".
|
||||||
Same object as returned by <i>File.solve()</i> method. You can also call it without paramer in which case it will clear any previously set values.</li>
|
Same object as returned by <i>File.solve()</i> method. You can also call it without paramer in which case it will clear any previously set values.</li>
|
||||||
|
<li><b>getSolverProfile()</b> return solver profile as Object.
|
||||||
|
<pre>var profile = core.getSolverProfile();
|
||||||
|
core.log(JSON.stringify(profile));</pre></li>
|
||||||
|
<li><b>setSolverProfile(index)</b> set solver profile by index. Valid values are from 1 to 8 as in GUI.</li>
|
||||||
|
<li><b>setSolverProfile(profile)</b> set solver profile. Parameter is same as object returned by <i>getSolverProfile()</i> method</li>
|
||||||
|
<li><b>question(question, buttons, title = "")</b> show dialog with question. First argument <i>question</i> is string. Second argument <i>buttons</i>
|
||||||
|
is array of following strings. "ok", "yes", "no", "yesall", "noall", "abort", "retry", "ignore", "cancel", "discard", "apply", "reset" Third argument <i>title</i> is optional string that show in title bar.
|
||||||
|
It return button that was clicked as a string.
|
||||||
|
<pre>var button = core.question("Apply to all files?", ["yes", "no"]);</pre>
|
||||||
|
</li>
|
||||||
|
<li><b>plot(graph)</b> this method show graph defined by JS object.
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
var chart = {
|
||||||
|
"title": "Chart title", // Title that will show on top of chart
|
||||||
|
"legend":
|
||||||
|
{
|
||||||
|
"visible": true,// default is true
|
||||||
|
"align": "left" // allowed values are "top", "right", "bottom", "left". default is "top"
|
||||||
|
},
|
||||||
|
"series":[ // array of data series
|
||||||
|
{
|
||||||
|
"title": "HFR",
|
||||||
|
"type": "bar", // type of the serie. Can be one of "line", "points", "linePoints", "bar"
|
||||||
|
"y":[2.5,3.1,2.6,2.2] // array of values
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ecc",
|
||||||
|
"y":[0.37, 0.4, 0.35, 0.25],
|
||||||
|
"color": "red" // color of serie. It can be name of color like "green" or RGB hex value "#ccddff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Stars",
|
||||||
|
"type": "points", // type of serie. can be "line", "points", "linePoints" and "bar". Default is "line"
|
||||||
|
"shape": "star", // shape of markers. valid only for points
|
||||||
|
"x":[1, 2.5, 3.5, 6],// line, points and linePoints can also have "x" values. Otherwise it assume sequence 0,1,2 ...
|
||||||
|
"y":[523,412,487,510],
|
||||||
|
"y2": true, // if set to true this serie will use secondary Y axis
|
||||||
|
"bestFit": true, // show best fit line
|
||||||
|
"color": "#0000ff"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
core.plot(chart);
|
||||||
|
</pre>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h4>File</h4>
|
<h4>File</h4>
|
||||||
In <b>files</b> array there are instances of type <b>File</b> objects that have these methods.
|
<p>In <b>files</b> array there are instances of type <b>File</b> objects that have these methods.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><b>fileName()</b> returns the name of the file, excluding the path.</li>
|
<li><b>fileName()</b> returns the name of the file, excluding the path.</li>
|
||||||
<li><b>absoluteFilePath()</b> returns an absolute path including the file name.</li>
|
<li><b>absoluteFilePath()</b> returns an absolute path including the file name.</li>
|
||||||
@@ -196,24 +284,34 @@ In <b>files</b> array there are instances of type <b>File</b> objects that have
|
|||||||
<li><b>fitsRecords()</b> return array of objects with properties <b>key, value</b> and <b>comment</b> </li>
|
<li><b>fitsRecords()</b> return array of objects with properties <b>key, value</b> and <b>comment</b> </li>
|
||||||
<li><b>modifyFITSRecords(FITSRecordModify)</b> modify FITS header by adding, removing or updating FITS record. Return true on success. Refer to <i>FITSRecordModify</i></li>
|
<li><b>modifyFITSRecords(FITSRecordModify)</b> modify FITS header by adding, removing or updating FITS record. Return true on success. Refer to <i>FITSRecordModify</i></li>
|
||||||
<li><b>isMarked()</b> return true if file is marked.</li>
|
<li><b>isMarked()</b> return true if file is marked.</li>
|
||||||
<li><b>copy(newpath)</b> copy file to new location. It return instance of new <i>File<i> object that represent this copied file. This path can be relative or absolute. In case that <i>newpath</i> parameter is relative
|
<li><b>copy(newpath)</b> copy file to new location. It return instance of new <i>File</i> object that represent this copied file. This path can be relative or absolute. In case that <i>newpath</i> parameter is relative
|
||||||
path then it "Output directory" from GUI windows is used as base directory. Parameter <i>newpath</i> can absolute path. File is then copied to this path. In case that copy fail it return null.</li>
|
path then it "Output directory" from GUI windows is used as base directory. Parameter <i>newpath</i> can absolute path. File is then copied to this path. In case that copy fail it return null.</li>
|
||||||
<li><b>move(newpath)</b> move file to new location. It return false if move failed. This can happend if destination is not writable but also if destination file already exist. This functions does not overwrite existing file.
|
<li><b>move(newpath)</b> move file to new location. It return false if move failed. This can happend if destination is not writable but also if destination file already exist. This functions does not overwrite existing file.
|
||||||
This path can be relative or absolute. In case that <i>newpath</i> parameter is relative path then it "Output directory" from GUI windows is used as base directory. Parameter <i>newpath</i> can be absolute path.
|
This path can be relative or absolute. In case that <i>newpath</i> parameter is relative path then it "Output directory" from GUI windows is used as base directory. Parameter <i>newpath</i> can be absolute path.
|
||||||
File is then moved to this path.</li>
|
File is then moved to this path.</li>
|
||||||
<li><b>convert(outpath, format, params)</b> convert image file from any format that program is able to open into FITS, XISF, JPEG, PNG, BMP.
|
<li><b>convert(outpath, format, params)</b> convert image file from any format that program is able to open into FITS, XISF, JPEG, PNG, BMP.
|
||||||
Parameters are: <i>outputpath</i> path where converted image will be saved. It automatically replace suffix according to format. <i>format</i> one of "FITS" "XISF", "JPG", "PNG", "TIFF" or "BMP". <i>params</i> object with attributes "compressionType" and "compressionLevel".
|
Parameters are: <i>outputpath</i> path where converted image will be saved. It automatically replace suffix according to format. This method return new instance of <i>File</i> that point to converted file.
|
||||||
Valid values for compressionType are be "gzip" or "rice" when converting to FITS. When converting to XISF compressionType can be "zlib", "lz4", "lz4hc", "zstd", "zlib+sh", "lz4+sh", "lz4hc+sh", "zstd+sh".
|
<i>format</i> one of "FITS" "XISF", "JPG", "PNG", "TIFF" or "BMP".
|
||||||
It is recommended to use "+sh" variants of compression.
|
<i>params</i> object with attributes
|
||||||
XISF format also accept "compressionLevel" in range 0-100 where zero is fastest compression and 100 slowest. If you omit this attribute or set it to -1 then default compression level will be used.
|
<ul>
|
||||||
It return new instance of <i>File</i> that point to converted file.
|
<li>"compressionLevel" used with XISF format. integer value between 0-100 determining speed and compression ratio.</li>
|
||||||
|
<li>"compressionType" for FITS format it can be "gzip" and "rice". For XISF it can be "zlib", "lz4", "lz4hc", "zstd", "zlib+sh", "lz4+sh", "lz4hc+sh", "zstd+sh"</li>
|
||||||
|
<li>"binning" any integer value above 1 will perform integer downsample</li>
|
||||||
|
<li>"average" by default set to true. If you set to false it will sum pixel values instead of averaging when performing binning.</li>
|
||||||
|
<li>"resize" downsample image to defined width and height by subobject <code>"resize":{"width": 128, "height": 128, "aspect":"keep"}</code>
|
||||||
|
"aspect" determine how to handle aspect ration when resizing image. "keep" and "expand" preserve original aspect ratio. Difference is that "keep" resulting image will be at most requested size
|
||||||
|
"epand" resulting size will be at least requested size. For example input image 800x600 pixels and resizing to 128x128. With keep resulting image will be 128x96 while with epxand it will be 170x128.
|
||||||
|
If set to "ignore" then resulting image will be exact size 128x128 ignoring original aspect ratio. By default keep is used.</li>
|
||||||
|
<li>"autostretch" when set to true it apply automatic stretch function to pixel values. By default it is set to false.</li>
|
||||||
|
</ul>
|
||||||
|
In case that both binning and resizing is set binning is performed first then resing.
|
||||||
<pre>file.convert("converted_file.xisf", "xisf", {"compressionType": "zstd+sh", "compressionLevel": 70});
|
<pre>file.convert("converted_file.xisf", "xisf", {"compressionType": "zstd+sh", "compressionLevel": 70});
|
||||||
file.convert("converted_file.fits", "fits", {"compressionType": "rice"});
|
file.convert("converted_file.fits", "fits", {"compressionType": "rice"});
|
||||||
file.convert("converted_file.jpg", "png");</pre>
|
file.convert("converted_file.jpg", "png");
|
||||||
</li>
|
file.convert("thumbnail.jpg", "jpg", {"binning": 2, "average": true, "resize": {"width":256, "height": 256, "aspect":"ignore"}, "autostretch": true});</pre></li>
|
||||||
<li><b>convertAsync(outpath, format, params)</b> same as previous method but it does conversion in separated thread asynchronously and in parallel. Before calling any method on object returned by this method you must call
|
<li><b>convertAsync(outpath, format, params)</b> same as previous method but it does conversion in separated thread asynchronously and in parallel.
|
||||||
<code>core.sync();</code> to ensure that conversion is done and destination file exists.
|
Before calling any method on object returned by this method you must call <code>core.sync();</code> to ensure that conversion is done and destination file exists.
|
||||||
<pre>let compression = {"compressionType": "zstd+sh"};
|
<pre>let compression = {"compressionType": "zstd+sh"};
|
||||||
let convertedFiles = [];
|
let convertedFiles = [];
|
||||||
for(file in files)
|
for(file in files)
|
||||||
{
|
{
|
||||||
@@ -239,8 +337,8 @@ core.log("Median value is " + s.median);</pre></li>
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h4>FITSRecordModify</h4>
|
<h4>FITSRecordModify</h4>
|
||||||
This class is used to define modify operation FITS header in FITS and XISF files. It can remove update and add records. Order of operation is also remove then update and last add.
|
<p>This class is used to define modify operation FITS header in FITS and XISF files. It can remove update and add records. Order of operation is also remove then update and last add.
|
||||||
The keyword names may be up to 8 characters long and can only contain uppercase letters, the digits 0-9, the hyphen, and the underscore character.
|
The keyword names may be up to 8 characters long and can only contain uppercase letters, the digits 0-9, the hyphen, and the underscore character.</p>
|
||||||
<pre>let modify = new FITSRecordModify();
|
<pre>let modify = new FITSRecordModify();
|
||||||
modify.updateKeyword("OBJECT", "M42");
|
modify.updateKeyword("OBJECT", "M42");
|
||||||
modify.updateKeyword("MYTILE", "PART1", "adding custom keyword so WBPP can group it");
|
modify.updateKeyword("MYTILE", "PART1", "adding custom keyword so WBPP can group it");
|
||||||
|
|||||||
+102
@@ -102,6 +102,60 @@ En appuyant sur la touche Enter ou en cliquant sur le bouton <i>Filtre</i>, les
|
|||||||
Cet exemple filtre les fichiers où : "Bias" figure dans le nom de fichier, la propriété OBJECT est "M_42" (où le trait de soulignement peut être n'importe quel caractère) et la propriété DATE commence par "2022".
|
Cet exemple filtre les fichiers où : "Bias" figure dans le nom de fichier, la propriété OBJECT est "M_42" (où le trait de soulignement peut être n'importe quel caractère) et la propriété DATE commence par "2022".
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h3>Database tree</h3>
|
||||||
|
<p>This is another view that show indexed database as tree. You can add or remove tree filter that construct a tree structure from FITS keywords. Each level of tree
|
||||||
|
will be based on this filter. You can specify one keywords multiple times.</p>
|
||||||
|
|
||||||
|
<h3>Plate Solving</h3>
|
||||||
|
<p>This module can plate solve images and update FITS header with solution for FITS and XISF images.
|
||||||
|
<b>Profile</b> this set various parameters that affect star extraction and solving.
|
||||||
|
<b>Starting point</b> program will try to automatically determine optimal starting point which helps to speed up solving.
|
||||||
|
You can leave one or both unchecked then it will attempt to do blind solving. If the position or scale is wrong it can actually
|
||||||
|
fail to solve.
|
||||||
|
<b>Solution</b> this section contain resulting solution like RA,DEC coordinates center of image, image field of view, orientation as degrees E of N,
|
||||||
|
image scale in arcseconds per pixel, number of stars extracted and HFR fitting and eccentricity. Then there is log window for debug information from
|
||||||
|
solver.
|
||||||
|
</p>
|
||||||
|
<p>Then finally there are various action button. Settings button show dialog where you can set path to existing index files or auto download some.
|
||||||
|
Extract button will just extract stars from image and it will show their count, HFR and eccentricity. This action doesn't need index files.
|
||||||
|
Solve button will try to find coordinates of images. Abort button will stop extraction or solving. Update FITS header will update FITS fits keywords
|
||||||
|
with found solution.</p>
|
||||||
|
|
||||||
|
<p>In settings dialog you can set path to index files which is by default custom internal one. It also try to locate commonly used path from other
|
||||||
|
programs like KStars for astrometry.net index files.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>File Manager</h3>
|
||||||
|
<p>
|
||||||
|
This is simple double panel file manager. It can show columns with selected FITS keywords. Each panel have tabs where it easily switch between
|
||||||
|
multiple directories. You can copy or move files and directories either inside one panel or between two panels by selecting and then dragging.
|
||||||
|
By default files are copies. To move then press Shift key before start dragging. Double click on file will open it in main window if it is image
|
||||||
|
or other file it will open default program that is associated with it this file type. You can also drag file to other programs like from default
|
||||||
|
file explorer programs that are in system or from file explorer to this program.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In menu you can select which FITS keywords will be showed. Temporarilly disable loading FITS header or copy file paths of selected files as text.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Command line options</h3>
|
||||||
|
<p>
|
||||||
|
Tenmon can be executed from command line. It support these command line options.
|
||||||
|
<ul>
|
||||||
|
<li><i>--thumb, --thumbnail <path></i> Generate thumbnail and save it to path. It generate it from first file provided as argument.</li>
|
||||||
|
<li><i>-s, --size <size></i> size of generated thumbnail. Aspect ratio of input image is preserved.</li>
|
||||||
|
<li><i>--script <script></i> execute a script from file path same manner as executed from GUI.</li>
|
||||||
|
<li><i>--scriptarg <arg>>;</i> pass this string as variable scriptarg to a running script.</li>
|
||||||
|
<li><i>--outdir <dir></i> output dir for script execution. By default current working directory is used.</li>
|
||||||
|
<li><i>--noexit</i> by default application exit when execution of script specified with --script ends. This prefent that.</li>
|
||||||
|
<li><i>-h, --help</i> show help end exit.</li>
|
||||||
|
Any other arguments are taken as input paths. If only one file path is specified then that image is open and image list is populated by directory
|
||||||
|
containing that image. If directory is specified then it is same as selecting that directory in "Open directory recusivelly". If multiple files are
|
||||||
|
specified then image list will contain just these speicified images.
|
||||||
|
|
||||||
|
When exuecting script with --script then these paths are used as input files and directories as in "Batch processing"
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
<h3>Traitement par lot</h3>
|
<h3>Traitement par lot</h3>
|
||||||
|
|
||||||
Ce module permet d'écrire des scripts en JavaScript qui traitent des fichiers images. La fenêtre de traitement par lots se compose de trois parties principales. En haut se trouve la liste des fichiers et répertoires d'entrée.
|
Ce module permet d'écrire des scripts en JavaScript qui traitent des fichiers images. La fenêtre de traitement par lots se compose de trois parties principales. En haut se trouve la liste des fichiers et répertoires d'entrée.
|
||||||
@@ -135,6 +189,54 @@ Le deuxième paramètre est la valeur par défaut dans la zone de saisie. Les tr
|
|||||||
<li><b>getItem(items)</b> affiche une boîte de dialogue de sélection qui permet de sélectionner un élément dans un tableau d'éléments. Lorsque vous appuyez sur Annuler, il renvoie Undefined.</li>
|
<li><b>getItem(items)</b> affiche une boîte de dialogue de sélection qui permet de sélectionner un élément dans un tableau d'éléments. Lorsque vous appuyez sur Annuler, il renvoie Undefined.</li>
|
||||||
<li><b>setStartingSolution(solution)</b> with this you can set starting point and image scale. It accepth object with attributes "ra", "dec", "pixscale".
|
<li><b>setStartingSolution(solution)</b> with this you can set starting point and image scale. It accepth object with attributes "ra", "dec", "pixscale".
|
||||||
Same object as returned by <i>File.solve()</i> method. You can also call it without paramer in which case it will clear any previously set values.</li>
|
Same object as returned by <i>File.solve()</i> method. You can also call it without paramer in which case it will clear any previously set values.</li>
|
||||||
|
<li><b>getSolverProfile()</b> return solver profile as Object.
|
||||||
|
<pre>var profile = core.getSolverProfile();
|
||||||
|
core.log(JSON.stringify(profile));</pre></li>
|
||||||
|
<li><b>setSolverProfile(index)</b> set solver profile by index. Valid values are from 1 to 8 as in GUI.</li>
|
||||||
|
<li><b>setSolverProfile(profile)</b> set solver profile. Parameter is same as object returned by <i>getSolverProfile()</i> method</li>
|
||||||
|
<li><b>question(question, buttons, title = "")</b> show dialog with question. First argument <i>question</i> is string. Second argument <i>buttons</i>
|
||||||
|
is array of following strings. "ok", "yes", "no", "yesall", "noall", "abort", "retry", "ignore", "cancel", "discard", "apply", "reset" Third argument <i>title</i> is optional string that show in title bar.
|
||||||
|
It return button that was clicked as a string.
|
||||||
|
<pre>var button = core.question("Apply to all files?", ["yes", "no"]);</pre>
|
||||||
|
</li>
|
||||||
|
<li><b>plot(graph)</b> this method show graph defined by JS object.
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
var chart = {
|
||||||
|
"title": "Chart title", // Title that will show on top of chart
|
||||||
|
"legend":
|
||||||
|
{
|
||||||
|
"visible": true,// default is true
|
||||||
|
"align": "left" // allowed values are "top", "right", "bottom", "left". default is "top"
|
||||||
|
},
|
||||||
|
"series":[ // array of data series
|
||||||
|
{
|
||||||
|
"title": "HFR",
|
||||||
|
"type": "bar", // type of the serie. Can be one of "line", "points", "linePoints", "bar"
|
||||||
|
"y":[2.5,3.1,2.6,2.2] // array of values
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ecc",
|
||||||
|
"y":[0.37, 0.4, 0.35, 0.25],
|
||||||
|
"color": "red" // color of serie. It can be name of color like "green" or RGB hex value "#ccddff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Stars",
|
||||||
|
"type": "points", // type of serie. can be "line", "points", "linePoints" and "bar". Default is "line"
|
||||||
|
"shape": "star", // shape of markers. valid only for points
|
||||||
|
"x":[1, 2.5, 3.5, 6],// line, points and linePoints can also have "x" values. Otherwise it assume sequence 0,1,2 ...
|
||||||
|
"y":[523,412,487,510],
|
||||||
|
"y2": true, // if set to true this serie will use secondary Y axis
|
||||||
|
"bestFit": true, // show best fit line
|
||||||
|
"color": "#0000ff"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
core.plot(chart);
|
||||||
|
</pre>
|
||||||
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h4>File</h4>
|
<h4>File</h4>
|
||||||
|
|||||||
+124
-6
@@ -1,5 +1,7 @@
|
|||||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<title>Pomocník</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
h1, h2, h3, h4 { padding:0px; margin:10px; }
|
h1, h2, h3, h4 { padding:0px; margin:10px; }
|
||||||
p { padding:0px; margin:5px 5px 10px 5px; }
|
p { padding:0px; margin:5px 5px 10px 5px; }
|
||||||
@@ -8,21 +10,21 @@ p { padding:0px; margin:5px 5px 10px 5px; }
|
|||||||
<body>
|
<body>
|
||||||
<h2>Tenmon pomocník</h2>
|
<h2>Tenmon pomocník</h2>
|
||||||
|
|
||||||
<p>Tenmon slúži primárne na zobrazenie astronomických fotiek a obrázkov. Dokáže otvoriť nasledovné formáty:
|
<p>Tenmon slúži primárne na zobrazenie astronomických fotiek a obrázkov. Dokáže otvoriť nasledovné formáty:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>FITS 8, 16, 32 bitové celočíselné 32 a 64 bitové s plávajúcou čiarkou</li>
|
<li>FITS 8, 16, 32 bitové celočíselné 32 a 64 bitové s plávajúcou čiarkou</li>
|
||||||
<li>XISF 8, 16, 32 bitové celočíselné 32 a 64 bitové s plávajúcou čiarkou</li>
|
<li>XISF 8, 16, 32 bitové celočíselné 32 a 64 bitové s plávajúcou čiarkou</li>
|
||||||
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM a SVG obrázky</li>
|
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM a SVG obrázky</li>
|
||||||
<li>CR2, CR3, NEF, DNG raw obrázky</li>
|
<li>CR2, CR3, NEF, DNG raw obrázky</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Hlavné okno</h3>
|
<h3>Hlavné okno</h3>
|
||||||
<p>V hlavnom okne sa zobrazujú načítané obrázky. Naľavo sú potom <i>Informácie o obrázku</i> kde sa zobrazujú podrobné
|
<p>V hlavnom okne sa zobrazujú načítané obrázky. Naľavo sú potom <i>Informácie o obrázku</i> kde sa zobrazujú podrobné
|
||||||
informácie o aktuálnom obrázku a <i>Zoznam súborov</i> kde sú všetky obrázky z adresára kde je aktuálne zobrazený obrázok.
|
informácie o aktuálnom obrázku a <i>Zoznam súborov</i> kde sú všetky obrázky z adresára kde je aktuálne zobrazený obrázok.
|
||||||
Hore je hlavné menu a pod ním je <i>Panel úrovní</i>. Všetky panely sa dajú zavrieť a presúvať. Zatvorený panel sa dá znova
|
Hore je hlavné menu a pod ním je <i>Panel úrovní</i>. Všetky panely sa dajú zavrieť a presúvať. Zatvorený panel sa dá znova
|
||||||
zobraziť v menu <i>Dokovacie panely</i>.
|
zobraziť v menu <i>Dokovacie panely</i>.</p>
|
||||||
</p>
|
<p>Na spodnom okraji okna je lišta v ktorej sa ukazuje aktuálna hodnota pixelu pod kurzorom a ak má obrázok WCS dátá aj aktuálne
|
||||||
|
celestiálne koordináty.</p>
|
||||||
|
|
||||||
<h3>Otváranie a ukladanie obrázkov</h3>
|
<h3>Otváranie a ukladanie obrázkov</h3>
|
||||||
<p>Otvoriť obrázok je možné v menu <i>Súbor->Otvoriť</i>. Po vybraní súboru ktorý sa má otvoriť je
|
<p>Otvoriť obrázok je možné v menu <i>Súbor->Otvoriť</i>. Po vybraní súboru ktorý sa má otvoriť je
|
||||||
@@ -43,13 +45,24 @@ hlavné okno na celú obrazovku. <i>Náhľady</i> zobrazí malé náhľady pre v
|
|||||||
<p>
|
<p>
|
||||||
Tento panel umožňuje upraviť spôsob ako sa zobrazujú obrazové dáta. Ako prvá je na tomto panely posuvná škála
|
Tento panel umožňuje upraviť spôsob ako sa zobrazujú obrazové dáta. Ako prvá je na tomto panely posuvná škála
|
||||||
na ktorej sa dajú nastaviť tri body.
|
na ktorej sa dajú nastaviť tri body.
|
||||||
<br><br><img src=":/about/stretch-panel.png">
|
<br><br><img src=":/about/stretch-panel.png"></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>čierny bod - všetky pixeli s hodnotou menšou ako nastavená budú zobrazené ako čierne</li>
|
<li>čierny bod - všetky pixeli s hodnotou menšou ako nastavená budú zobrazené ako čierne</li>
|
||||||
<li>stredný bod - pixeli s touto hodnotou budú zobrazené ako 50% šedá</li>
|
<li>stredný bod - pixeli s touto hodnotou budú zobrazené ako 50% šedá</li>
|
||||||
<li>biely bod - pixeli nad touto hodnotou budú zobrazené ako biele</li>
|
<li>biely bod - pixeli nad touto hodnotou budú zobrazené ako biele</li>
|
||||||
</ul>
|
</ul>
|
||||||
Prvé tlačidlo prepína prepojenie nastavenia čierneho, stretdného a bieleho bodu. Po prepnutí sa dá každý farebný kanál nastaviť
|
<p>Nasleduje 8 tlačidiel pre nastavenie zobrazenie obrázka:</p>
|
||||||
|
<ul>
|
||||||
|
<li><i>Prepojené kanály</i> prepína medzi nataihnutím jasových urovní pre každý RGB kanál zvlášť alebo jednotné pre všetky kanály.</li>
|
||||||
|
<li><i>Automatické natiahnutie</i> automaticky nastavý čierny, šedý a biely bod pre optimálne zobrazenie..</li>
|
||||||
|
<li><i>Resetuj funkciu prevodu na obrazovku</i> nastavý hodnoty čierneho, šedého a bieleho bodu na východzie hodnoty.</li>
|
||||||
|
<li><i>Invertuj farby</i> invertuje zobrazené farby a zobrazý obrázok ako negatív.</li>
|
||||||
|
<li><i>Falošné farby</i> pre zobrazenie čiernobielych obrázkov sa použije farebná paleta.</li>
|
||||||
|
<li><i>Preved CFA na farbu</i> Demosaicing black and white image from CFA sensor to color one.</li>
|
||||||
|
<li><i>Aplikuj automatické natiahnutie pri načítaní</i> pri zapnutí </li>
|
||||||
|
<li><i>Vykresli equatoriálnu mriežku</i> zapína zobrazenie equatoriálnej mriežky. Je poitrebné aby súbor obsahoval WCS dáta.</li>
|
||||||
|
</ul>
|
||||||
|
<p>Prvé tlačidlo prepína prepojenie nastavenia čierneho, stretdného a bieleho bodu. Po prepnutí sa dá každý farebný kanál nastaviť
|
||||||
samostatne.
|
samostatne.
|
||||||
Nasleduje tlačidlo ktoré nastaví hodnoty čierneho a stredného bodu tak aby bol obrázok zobrazený optimálnym jasom.
|
Nasleduje tlačidlo ktoré nastaví hodnoty čierneho a stredného bodu tak aby bol obrázok zobrazený optimálnym jasom.
|
||||||
Druhé tlačidlo resetuje hodnoty pre čierny, stredný a biely bod na východzie hodnoty. Invertovanie farieb zobrazí obrázok ako negatív.
|
Druhé tlačidlo resetuje hodnoty pre čierny, stredný a biely bod na východzie hodnoty. Invertovanie farieb zobrazí obrázok ako negatív.
|
||||||
@@ -92,6 +105,57 @@ V nasledovnom príklade sa vyhľadajú súbory ktoré majú v mene súboru "Bias
|
|||||||
zástupný znak za hocijaký reťazec znakov aj žiadny. Znak _ je tiež zástupný znak zastupujúci práve jeden znak.
|
zástupný znak za hocijaký reťazec znakov aj žiadny. Znak _ je tiež zástupný znak zastupujúci práve jeden znak.
|
||||||
Bez použitia zástupných znakov sa vyhľadá iba presný výskyt.</p>
|
Bez použitia zástupných znakov sa vyhľadá iba presný výskyt.</p>
|
||||||
|
|
||||||
|
<h3>Database tree</h3>
|
||||||
|
<p>This is another view that show indexed database as tree. You can add or remove tree filter that construct a tree structure from FITS keywords. Each level of tree
|
||||||
|
will be based on this filter. You can specify one keywords multiple times.</p>
|
||||||
|
|
||||||
|
<h3>Plate Solving</h3>
|
||||||
|
<p>Tento modul umožnuje vyriešiť obrázok a určiť RA, DEC koordináty a aktualizovať FITS a XISF súbory s WCS dátami.
|
||||||
|
<b>Profil</b> toto nastavuje rôzne parametre ovplivňujúce extrahovanie hviezd a hľadanie koordinátov.
|
||||||
|
<b>Štartovný bod</b> program sa pokúsi automaticky určiť bod začiatku hľadania koordinátov pre zrýchlenie hľadania.
|
||||||
|
Možete nechať jednu alebo obydve voľbi nezaškrtnuté kedy sa bude hľadať riešenie naslepo. Ak je pozícia alebo škála nesprávna hľadanie môže zlyhať.
|
||||||
|
<b>Riešenie</b> táto časť obsahuje výsledné riešenie hľadania ako sú RA,DEC koordináty stredu obrázku, veľkosť zorného poľa, uhol natočenia v stupňoch,
|
||||||
|
škála obrázku v arcsekundách na pixel, počet nájdených hviezd a veľkosť hviezd HFR a excentricita. Potom je tu ešte okno so záznamom z riešenia.
|
||||||
|
</p>
|
||||||
|
<p>Pod tým je tlačidlo pre nastavenie cesty k indexovým súborom a ich stiahnutie. Je možné buď stiahnuť indexové súbory alebo použiť už existujúce súbory.
|
||||||
|
Tlačidlo Extrahovať nájde hviezdy a zobrazý ich počet veľkosť HFR a excentricitu. Na toto nie sú potrebné indexové súbory.
|
||||||
|
Tlačidlo Vyriešiť sa pokúsi nájsť koordináty obrázka. Zrušiť preruší hľadanie riešenia. Aktualizovať FITS hlavičku zapíše najdené riešenie do súboru.</p>
|
||||||
|
|
||||||
|
<p>In settings dialog you can set path to index files which is by default custom internal one. It also try to locate commonly used path from other
|
||||||
|
programs like KStars for astrometry.net index files.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>File Manager</h3>
|
||||||
|
<p>
|
||||||
|
This is simple double panel file manager. It can show columns with selected FITS keywords. Each panel have tabs where it easily switch between
|
||||||
|
multiple directories. You can copy or move files and directories either inside one panel or between two panels by selecting and then dragging.
|
||||||
|
By default files are copies. To move then press Shift key before start dragging. Double click on file will open it in main window if it is image
|
||||||
|
or other file it will open default program that is associated with it this file type. You can also drag file to other programs like from default
|
||||||
|
file explorer programs that are in system or from file explorer to this program.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In menu you can select which FITS keywords will be showed. Temporarilly disable loading FITS header or copy file paths of selected files as text.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Command line options</h3>
|
||||||
|
<p>
|
||||||
|
Tenmon can be executed from command line. It support these command line options.
|
||||||
|
<ul>
|
||||||
|
<li><i>--thumb, --thumbnail <path></i> Generate thumbnail and save it to path. It generate it from first file provided as argument.</li>
|
||||||
|
<li><i>-s, --size <size></i> size of generated thumbnail. Aspect ratio of input image is preserved.</li>
|
||||||
|
<li><i>--script <script></i> execute a script from file path same manner as executed from GUI.</li>
|
||||||
|
<li><i>--scriptarg <arg>>;</i> pass this string as variable scriptarg to a running script.</li>
|
||||||
|
<li><i>--outdir <dir></i> output dir for script execution. By default current working directory is used.</li>
|
||||||
|
<li><i>--noexit</i> by default application exit when execution of script specified with --script ends. This prefent that.</li>
|
||||||
|
<li><i>-h, --help</i> show help end exit.</li>
|
||||||
|
Any other arguments are taken as input paths. If only one file path is specified then that image is open and image list is populated by directory
|
||||||
|
containing that image. If directory is specified then it is same as selecting that directory in "Open directory recusivelly". If multiple files are
|
||||||
|
specified then image list will contain just these speicified images.
|
||||||
|
|
||||||
|
When exuecting script with --script then these paths are used as input files and directories as in "Batch processing"
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
<h3>Hromadné spracovanie</h3>
|
<h3>Hromadné spracovanie</h3>
|
||||||
|
|
||||||
Tento modul umožnuje písanie skriptov v JavaScripte ktoré spracujú súbory obrázkov. Okno Hromadného spracovanie pozostáva z troch častí. Navrchu je zoznam vstupných súborov a adresárov.
|
Tento modul umožnuje písanie skriptov v JavaScripte ktoré spracujú súbory obrázkov. Okno Hromadného spracovanie pozostáva z troch častí. Navrchu je zoznam vstupných súborov a adresárov.
|
||||||
@@ -107,6 +171,12 @@ V skripte potom cez toto pole iteruje nasledovne.
|
|||||||
}
|
}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
<p>Pod týmto zoznamom je výstupný adresár. Všetky relatívne cesty predané do metod ako sú copy(), move() alebo convert() budú relatívne voči tomuto adresáru.
|
||||||
|
Ak je ako argument použitá absolútna cesta tak je tento vystupný adresár ignorovaný.</p>
|
||||||
|
|
||||||
|
<p>Nasleduje logovacie okno kde sú zapisováné všetký výpisy z behu scriptu. Hlavne volania z <code>core.log()</code> Na spodu je konzola kde je možné vkladať jednoduché príkazy a nakoniec ešte tlačítka
|
||||||
|
ktoré spúštať alebo zastavovať vybraný skript.</p>
|
||||||
|
|
||||||
<h4>core</h4>
|
<h4>core</h4>
|
||||||
V skripte je dostupný globálny objekt nazvaný <b>core</b> ktorý má nasledovné metódy.
|
V skripte je dostupný globálny objekt nazvaný <b>core</b> ktorý má nasledovné metódy.
|
||||||
<ul>
|
<ul>
|
||||||
@@ -124,6 +194,54 @@ V skripte je dostupný globálny objekt nazvaný <b>core</b> ktorý má nasledov
|
|||||||
<li><b>getItem(items)</b> ukáže dialog pre výber jednej hodnoty z poľa hodnôt. Vracia vybranú hodnotu ako String alebo ak je stlačené tlačidlo zrušiť vráti Undefined.</li>
|
<li><b>getItem(items)</b> ukáže dialog pre výber jednej hodnoty z poľa hodnôt. Vracia vybranú hodnotu ako String alebo ak je stlačené tlačidlo zrušiť vráti Undefined.</li>
|
||||||
<li><b>setStartingSolution(solution)</b> with this you can set starting point and image scale. It accepth object with attributes "ra", "dec", "pixscale".
|
<li><b>setStartingSolution(solution)</b> with this you can set starting point and image scale. It accepth object with attributes "ra", "dec", "pixscale".
|
||||||
Same object as returned by <i>File.solve()</i> method. You can also call it without paramer in which case it will clear any previously set values.</li>
|
Same object as returned by <i>File.solve()</i> method. You can also call it without paramer in which case it will clear any previously set values.</li>
|
||||||
|
<li><b>getSolverProfile()</b> return solver profile as Object.
|
||||||
|
<pre>var profile = core.getSolverProfile();
|
||||||
|
core.log(JSON.stringify(profile));</pre></li>
|
||||||
|
<li><b>setSolverProfile(index)</b> set solver profile by index. Valid values are from 1 to 8 as in GUI.</li>
|
||||||
|
<li><b>setSolverProfile(profile)</b> set solver profile. Parameter is same as object returned by <i>getSolverProfile()</i> method</li>
|
||||||
|
<li><b>question(question, buttons, title = "")</b> show dialog with question. First argument <i>question</i> is string. Second argument <i>buttons</i>
|
||||||
|
is array of following strings. "ok", "yes", "no", "yesall", "noall", "abort", "retry", "ignore", "cancel", "discard", "apply", "reset" Third argument <i>title</i> is optional string that show in title bar.
|
||||||
|
It return button that was clicked as a string.
|
||||||
|
<pre>var button = core.question("Apply to all files?", ["yes", "no"]);</pre>
|
||||||
|
</li>
|
||||||
|
<li><b>plot(graph)</b> this method show graph defined by JS object.
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
var chart = {
|
||||||
|
"title": "Chart title", // Title that will show on top of chart
|
||||||
|
"legend":
|
||||||
|
{
|
||||||
|
"visible": true,// default is true
|
||||||
|
"align": "left" // allowed values are "top", "right", "bottom", "left". default is "top"
|
||||||
|
},
|
||||||
|
"series":[ // array of data series
|
||||||
|
{
|
||||||
|
"title": "HFR",
|
||||||
|
"type": "bar", // type of the serie. Can be one of "line", "points", "linePoints", "bar"
|
||||||
|
"y":[2.5,3.1,2.6,2.2] // array of values
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ecc",
|
||||||
|
"y":[0.37, 0.4, 0.35, 0.25],
|
||||||
|
"color": "red" // color of serie. It can be name of color like "green" or RGB hex value "#ccddff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Stars",
|
||||||
|
"type": "points", // type of serie. can be "line", "points", "linePoints" and "bar". Default is "line"
|
||||||
|
"shape": "star", // shape of markers. valid only for points
|
||||||
|
"x":[1, 2.5, 3.5, 6],// line, points and linePoints can also have "x" values. Otherwise it assume sequence 0,1,2 ...
|
||||||
|
"y":[523,412,487,510],
|
||||||
|
"y2": true, // if set to true this serie will use secondary Y axis
|
||||||
|
"bestFit": true, // show best fit line
|
||||||
|
"color": "#0000ff"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
core.plot(chart);
|
||||||
|
</pre>
|
||||||
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h4>File</h4>
|
<h4>File</h4>
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<td style="padding-right:10px"><img src=":/space.nouspiro.tenmon.png"></td>
|
<td style="padding-right:10px"><img src=":/space.nouspiro.tenmon.png"></td>
|
||||||
<td><h3>Tenmon</h3>
|
<td><h3>Tenmon</h3>
|
||||||
Tenmon is FITS/XISF image viewer and converter. It also index FITS keywords.<br>
|
Tenmon is FITS/XISF image viewer and converter. It also index FITS keywords.<br>
|
||||||
v@GITVERSION@ Copyright © 2022 Dušan Poizl<br><br>
|
v@GITVERSION@ Copyright © 2026 Dušan Poizl<br><br>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify<br>
|
This program is free software: you can redistribute it and/or modify<br>
|
||||||
it under the terms of the GNU General Public License as published by<br>
|
it under the terms of the GNU General Public License as published by<br>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
|
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
|
||||||
find_program(XDG-ICON-RESOURCE_EXECUTABLE xdg-icon-resource)
|
find_program(XDG-ICON-RESOURCE_EXECUTABLE xdg-icon-resource)
|
||||||
|
find_program(XDG-MIME xdg-mime)
|
||||||
execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor space.nouspiro.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor space.nouspiro.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 resources/space.nouspiro.tenmon.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 resources/space.nouspiro.tenmon.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 128 resources/space.nouspiro.tenmon_128.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 128 resources/space.nouspiro.tenmon_128.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
execute_process(COMMAND ${XDG-MIME} install --novendor space.nouspiro.tenmon.xisf.xml WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
|||||||
+1
-1
Submodule libXISF updated: 9a32138f6a...7b70b6a081
-775
@@ -1,775 +0,0 @@
|
|||||||
#include "loadrunable.h"
|
|
||||||
#include "imageringlist.h"
|
|
||||||
#include <libraw/libraw.h>
|
|
||||||
#include "imageinfo.h"
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <iostream>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <libexif/exif-data.h>
|
|
||||||
#include <fitsio2.h>
|
|
||||||
#include <libxisf.h>
|
|
||||||
#include "rawimage.h"
|
|
||||||
#include "starfit.h"
|
|
||||||
#include <lcms2.h>
|
|
||||||
|
|
||||||
QString makeMaxPath(QString path)
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_WIN64
|
|
||||||
if(!path.startsWith("\\\\?\\"))
|
|
||||||
{
|
|
||||||
QFileInfo info(path);
|
|
||||||
path = info.absoluteFilePath();
|
|
||||||
path = QDir::toNativeSeparators(path);
|
|
||||||
path.prepend("\\\\?\\");
|
|
||||||
qDebug() << path;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) :
|
|
||||||
m_file(makeMaxPath(file)),
|
|
||||||
m_receiver(receiver),
|
|
||||||
m_analyzeLevel(level),
|
|
||||||
m_thumbnail(thumbnail)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadExifEntry(ImageInfoData &info, ExifContent *content, ExifTag tag)
|
|
||||||
{
|
|
||||||
char val[1024];
|
|
||||||
ExifEntry *entry = exif_content_get_entry(content, tag);
|
|
||||||
if(entry)
|
|
||||||
{
|
|
||||||
exif_entry_get_value(entry, val, sizeof(val));
|
|
||||||
info.info.append({exif_tag_get_title(tag), val});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawPeaks(QImage &img, const std::vector<Peak> &peaks)
|
|
||||||
{
|
|
||||||
QPixmap pix = QPixmap::fromImage(img);
|
|
||||||
QPainter painter(&pix);
|
|
||||||
painter.setPen(Qt::red);
|
|
||||||
for(auto peak : peaks)
|
|
||||||
{
|
|
||||||
painter.drawEllipse(QPoint(peak.x(), peak.y()), 5, 5);
|
|
||||||
}
|
|
||||||
img = pix.toImage();
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawStars(QImage &img, const std::vector<Star> &stars)
|
|
||||||
{
|
|
||||||
QPixmap pix = QPixmap::fromImage(img);
|
|
||||||
QPainter painter(&pix);
|
|
||||||
painter.setPen(Qt::red);
|
|
||||||
for(auto star : stars)
|
|
||||||
{
|
|
||||||
painter.drawEllipse(QPointF(star.m_x, star.m_y), star.hw20X(), star.hw20Y());
|
|
||||||
}
|
|
||||||
img = pix.toImage();
|
|
||||||
}
|
|
||||||
|
|
||||||
void printStarModel(int radius, const std::vector<double> &data, const Star &star)
|
|
||||||
{
|
|
||||||
QString d = "d=[";
|
|
||||||
QString m = "m=[";
|
|
||||||
for(int y=0; y<radius; y++)
|
|
||||||
{
|
|
||||||
for(int x=0; x<radius; x++)
|
|
||||||
{
|
|
||||||
d += QString::number(data[y*radius+x]) + ",";
|
|
||||||
m += QString::number(gauss_model(star.m_am, star.m_x, star.m_y, star.m_sx, star.m_sy, x, y)) + ",";
|
|
||||||
}
|
|
||||||
d += ";";
|
|
||||||
m += ";";
|
|
||||||
}
|
|
||||||
d += "];";
|
|
||||||
m += "];";
|
|
||||||
//std::cout << star.m_am << " " << star.m_sx << star.m_sy << std::endl;
|
|
||||||
std::cout << d.toStdString() << std::endl;
|
|
||||||
std::cout << m.toStdString() << std::endl << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool loadRAW(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image)
|
|
||||||
{
|
|
||||||
std::unique_ptr<LibRaw> raw = std::make_unique<LibRaw>();
|
|
||||||
raw->open_file(path.toLocal8Bit().data());
|
|
||||||
raw->imgdata.params.half_size = true;
|
|
||||||
raw->imgdata.params.use_camera_wb = true;
|
|
||||||
raw->imgdata.params.user_flip = 0;
|
|
||||||
if(raw->unpack())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
|
|
||||||
libraw_rawdata_t rawdata = raw->imgdata.rawdata;
|
|
||||||
size_t size = rawdata.sizes.width*rawdata.sizes.height;
|
|
||||||
|
|
||||||
std::vector<uint16_t> out;
|
|
||||||
out.resize(size);
|
|
||||||
size_t d = 0;
|
|
||||||
uint h=rawdata.sizes.top_margin+rawdata.sizes.height;
|
|
||||||
uint w=rawdata.sizes.left_margin+rawdata.sizes.width;
|
|
||||||
size_t pitch = rawdata.sizes.raw_pitch/sizeof(uint16_t);
|
|
||||||
|
|
||||||
for(size_t i=rawdata.sizes.top_margin;i<h;i++)
|
|
||||||
{
|
|
||||||
for(size_t o=rawdata.sizes.left_margin;o<w;o++)
|
|
||||||
{
|
|
||||||
uint16_t p = rawdata.raw_image[i*pitch+o];
|
|
||||||
out[d++] = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
image = std::make_shared<RawImage>(rawdata.sizes.width, rawdata.sizes.height, 1, RawImage::UINT16);
|
|
||||||
memcpy(image->data(), &out[0], sizeof(uint16_t)*d);
|
|
||||||
|
|
||||||
QString shutterSpeed = QString::number(raw->imgdata.other.shutter);
|
|
||||||
if(raw->imgdata.other.shutter < 1)
|
|
||||||
{
|
|
||||||
shutterSpeed = QString("1/%1s").arg(1.0f/raw->imgdata.other.shutter);
|
|
||||||
}
|
|
||||||
info.info.append({QObject::tr("Width"), QString::number(raw->imgdata.sizes.width)});
|
|
||||||
info.info.append({QObject::tr("Height"), QString::number(raw->imgdata.sizes.height)});
|
|
||||||
info.info.append({QObject::tr("ISO"), QString::number(raw->imgdata.other.iso_speed)});
|
|
||||||
info.info.append({QObject::tr("Shutter speed"), shutterSpeed});
|
|
||||||
#if LIBRAW_MINOR_VERSION>=19
|
|
||||||
// info.append(StringPair(QObject::tr("Camera temperature"), QString::number(raw.imgdata.other.CameraTemperature)));
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int loadFITSHeader(fitsfile *file, ImageInfoData &info)
|
|
||||||
{
|
|
||||||
int imgtype;
|
|
||||||
int naxis;
|
|
||||||
long naxes[3] = {0};
|
|
||||||
int nexist;
|
|
||||||
int status = 0;
|
|
||||||
char key[FLEN_KEYWORD];
|
|
||||||
char val[FLEN_VALUE];
|
|
||||||
char comm[FLEN_COMMENT];
|
|
||||||
char strval[FLEN_VALUE];
|
|
||||||
QVariant var;
|
|
||||||
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
|
|
||||||
fits_get_hdrspace(file, &nexist, nullptr, &status);
|
|
||||||
for(int i=1; i<=nexist; i++)
|
|
||||||
{
|
|
||||||
fits_read_keyn(file, i, key, val, comm, &status);
|
|
||||||
fits_read_key(file, TSTRING, key, strval, nullptr, &status);
|
|
||||||
if(status == 0 || status == VALUE_UNDEFINED)
|
|
||||||
{
|
|
||||||
QString string(strval);
|
|
||||||
bool isint;
|
|
||||||
bool isdouble;
|
|
||||||
double vald = string.toDouble(&isdouble);
|
|
||||||
long long vall = string.toLongLong(&isint);
|
|
||||||
if(isint)
|
|
||||||
var = vall;
|
|
||||||
else if(isdouble)
|
|
||||||
var = vald;
|
|
||||||
else if(status == VALUE_UNDEFINED)
|
|
||||||
var = QVariant();
|
|
||||||
else if(string == "T" || string == "F")
|
|
||||||
var = string == "T";
|
|
||||||
else
|
|
||||||
var = string;
|
|
||||||
status = 0;
|
|
||||||
info.fitsHeader.append(FITSRecord(key, var, comm));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *header = nullptr;
|
|
||||||
int nrec = 0;
|
|
||||||
const char *exclist[] = {"PV1_1", "PV1_2"};
|
|
||||||
fits_hdr2str(file, TRUE, (char**)exclist, 2, &header, &nrec, &status);
|
|
||||||
if(status == 0)
|
|
||||||
{
|
|
||||||
info.wcs = std::make_shared<WCSDataT>(naxes[0], naxes[1], header, nrec);
|
|
||||||
if(!info.wcs->valid())info.wcs.reset();
|
|
||||||
}
|
|
||||||
fits_free_memory(header, &status);
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
|
|
||||||
{
|
|
||||||
fitsfile *file;
|
|
||||||
int status = 0;
|
|
||||||
int type = -1;
|
|
||||||
fits_open_diskfile(&file, path.toLocal8Bit().data(), READONLY, &status);
|
|
||||||
int num = 0;
|
|
||||||
fits_get_num_hdus(file, &num, &status);
|
|
||||||
|
|
||||||
int imgtype;
|
|
||||||
int naxis;
|
|
||||||
long naxes[3] = {0};
|
|
||||||
for(int i=1; i <= num; i++)
|
|
||||||
{
|
|
||||||
fits_movabs_hdu(file, i, IMAGE_HDU, &status);
|
|
||||||
fits_get_hdu_type(file, &type, &status);
|
|
||||||
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
|
|
||||||
fits_get_img_equivtype(file, &imgtype, &status);
|
|
||||||
|
|
||||||
if(type == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
|
|
||||||
{
|
|
||||||
RawImage::DataType type;
|
|
||||||
int fitstype;
|
|
||||||
long fpixel[3] = {1,1,1};
|
|
||||||
switch(imgtype)
|
|
||||||
{
|
|
||||||
case BYTE_IMG:
|
|
||||||
type = RawImage::UINT8;
|
|
||||||
fitstype = TBYTE;
|
|
||||||
break;
|
|
||||||
case SHORT_IMG:
|
|
||||||
type = RawImage::UINT16;
|
|
||||||
fitstype = TSHORT;
|
|
||||||
break;
|
|
||||||
case USHORT_IMG:
|
|
||||||
type = RawImage::UINT16;
|
|
||||||
fitstype = TUSHORT;
|
|
||||||
break;
|
|
||||||
case ULONG_IMG:
|
|
||||||
type = RawImage::UINT32;
|
|
||||||
fitstype = TUINT;
|
|
||||||
break;
|
|
||||||
case FLOAT_IMG:
|
|
||||||
type = RawImage::FLOAT32;
|
|
||||||
fitstype = TFLOAT;
|
|
||||||
break;
|
|
||||||
case DOUBLE_IMG:
|
|
||||||
type = RawImage::FLOAT64;
|
|
||||||
fitstype = TDOUBLE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
|
|
||||||
goto noload;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t size = naxes[0]*naxes[1];
|
|
||||||
size_t w = naxes[0];
|
|
||||||
size_t h = naxes[1];
|
|
||||||
|
|
||||||
info.info.append({QObject::tr("Width"), QString::number(naxes[0])});
|
|
||||||
info.info.append({QObject::tr("Height"), QString::number(naxes[1])});
|
|
||||||
|
|
||||||
RawImage img(w, h, naxis == 2 ? 1 : naxes[2], type);
|
|
||||||
uint8_t *data = static_cast<uint8_t*>(img.data());
|
|
||||||
for (int i=1; i==1 || i<=naxes[2]; i++)
|
|
||||||
{
|
|
||||||
fpixel[2] = i;
|
|
||||||
fits_read_pix(file, fitstype, fpixel, size, NULL, data + img.size() * RawImage::typeSize(type) * (i-1), NULL, &status);
|
|
||||||
}
|
|
||||||
if(fitstype == TSHORT)
|
|
||||||
{
|
|
||||||
uint16_t *s = static_cast<uint16_t*>(img.data());
|
|
||||||
size_t size = img.size() * img.channels();
|
|
||||||
for(size_t i=0; i<size; i++)
|
|
||||||
s[i] -= INT16_MIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(img.channels() == 1 || planar)
|
|
||||||
image = std::make_shared<RawImage>(std::move(img));
|
|
||||||
else
|
|
||||||
image = RawImage::fromPlanar(img);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
noload:
|
|
||||||
if(file)
|
|
||||||
loadFITSHeader(file, info);
|
|
||||||
|
|
||||||
if(image)
|
|
||||||
{
|
|
||||||
for(auto fits : info.fitsHeader)
|
|
||||||
{
|
|
||||||
if(fits.key == "ROWORDER" && fits.value == "BOTTOM-UP")
|
|
||||||
image->flip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fits_close_file(file, &status);
|
|
||||||
if(status)
|
|
||||||
{
|
|
||||||
char err[100];
|
|
||||||
fits_get_errstatus(status, err);
|
|
||||||
info.info.append({QObject::tr("Error"), QString(err)});
|
|
||||||
qDebug() << "Failed to load FITS file" << err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LibXISF::XISFReader xisf;
|
|
||||||
xisf.open(path.toLocal8Bit().data());
|
|
||||||
|
|
||||||
const LibXISF::Image &xisfImage = xisf.getImage(0);
|
|
||||||
|
|
||||||
auto fitskeywords = xisfImage.fitsKeywords();
|
|
||||||
for(auto fits : fitskeywords)
|
|
||||||
{
|
|
||||||
info.fitsHeader.append(fits);
|
|
||||||
}
|
|
||||||
auto imageproperties = xisfImage.imageProperties();
|
|
||||||
for(auto prop : imageproperties)
|
|
||||||
{
|
|
||||||
info.fitsHeader.append(prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
info.wcs = std::make_shared<WCSDataT>(xisfImage.width(), xisfImage.height(), info.fitsHeader);
|
|
||||||
info.info.append({QObject::tr("Width"), QString::number(xisfImage.width())});
|
|
||||||
info.info.append({QObject::tr("Height"), QString::number(xisfImage.height())});
|
|
||||||
if(!info.wcs->valid())info.wcs.reset();
|
|
||||||
|
|
||||||
RawImage::DataType type;
|
|
||||||
switch(xisfImage.sampleFormat())
|
|
||||||
{
|
|
||||||
case LibXISF::Image::UInt8: type = RawImage::UINT8; break;
|
|
||||||
case LibXISF::Image::UInt16: type = RawImage::UINT16; break;
|
|
||||||
case LibXISF::Image::UInt32: type = RawImage::UINT32; break;
|
|
||||||
case LibXISF::Image::Float32: type = RawImage::FLOAT32; break;
|
|
||||||
case LibXISF::Image::Float64: type = RawImage::FLOAT64; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
LibXISF::Image tmpImage = xisfImage;
|
|
||||||
tmpImage.convertPixelStorageTo(LibXISF::Image::Planar);
|
|
||||||
if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray)
|
|
||||||
{
|
|
||||||
image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
|
|
||||||
std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
|
|
||||||
image->setICCProfile(tmpImage.iccProfile());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
|
|
||||||
{
|
|
||||||
if(planar)
|
|
||||||
{
|
|
||||||
image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
|
|
||||||
std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
image = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
|
|
||||||
}
|
|
||||||
|
|
||||||
image->setICCProfile(tmpImage.iccProfile());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch (LibXISF::Error &err)
|
|
||||||
{
|
|
||||||
info.info.append(QPair<QString, QString>("Error", err.what()));
|
|
||||||
qDebug() << "Failed to load XISF" << err.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoadRunable::run()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(!m_thumbnail && !m_receiver->isCurrent())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QElapsedTimer timer;
|
|
||||||
ImageInfoData info;
|
|
||||||
QFileInfo finfo(m_file);
|
|
||||||
info.info.append({QObject::tr("Filename"), finfo.fileName()});
|
|
||||||
|
|
||||||
std::shared_ptr<RawImage> rawImage;
|
|
||||||
if(!loadImage(m_file, info, rawImage))
|
|
||||||
info.info.append({QObject::tr("Error"), QObject::tr("Failed to load image")});
|
|
||||||
|
|
||||||
|
|
||||||
if(rawImage && !m_thumbnail)
|
|
||||||
{
|
|
||||||
rawImage->convertToGLFormat();
|
|
||||||
timer.start();
|
|
||||||
rawImage->generateLUT();
|
|
||||||
qDebug() << "generate LUT" << timer.restart();
|
|
||||||
//rawImage->convertTosRGB();
|
|
||||||
//qDebug() << "convert" << timer.restart();
|
|
||||||
rawImage->calcStats();
|
|
||||||
const RawImage::Stats &stats = rawImage->imageStats();
|
|
||||||
qDebug() << "image stats" << timer.restart();
|
|
||||||
if(rawImage->channels() == 1)
|
|
||||||
{
|
|
||||||
info.info.append({QObject::tr("Mean"), QString::number(stats.m_mean[0])});
|
|
||||||
info.info.append({QObject::tr("Standart deviation"), QString::number(stats.m_stdDev[0])});
|
|
||||||
info.info.append({QObject::tr("Median"), QString::number(stats.m_median[0])});
|
|
||||||
info.info.append({QObject::tr("Minimum"), QString::number(stats.m_min[0])});
|
|
||||||
info.info.append({QObject::tr("Maximum"), QString::number(stats.m_max[0])});
|
|
||||||
info.info.append({QObject::tr("MAD"), QString::number(stats.m_mad[0])});
|
|
||||||
info.info.append({QObject::tr("Saturated"), QString::number(100.0 * stats.m_saturated[0] / rawImage->size()) + "%"});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
info.info.append({QObject::tr("Mean"), QString("%1 %2 %3").arg(stats.m_mean[0]).arg(stats.m_mean[1]).arg(stats.m_mean[2])});
|
|
||||||
info.info.append({QObject::tr("Standart deviation"), QString("%1 %2 %3").arg(stats.m_stdDev[0]).arg(stats.m_stdDev[1]).arg(stats.m_stdDev[2])});
|
|
||||||
info.info.append({QObject::tr("Median"), QString("%1 %2 %3").arg(stats.m_median[0]).arg(stats.m_median[1]).arg(stats.m_median[2])});
|
|
||||||
info.info.append({QObject::tr("Minimum"), QString("%1 %2 %3").arg(stats.m_min[0]).arg(stats.m_min[1]).arg(stats.m_min[2])});
|
|
||||||
info.info.append({QObject::tr("Maximum"), QString("%1 %2 %3").arg(stats.m_max[0]).arg(stats.m_max[1]).arg(stats.m_max[2])});
|
|
||||||
info.info.append({QObject::tr("MAD"), QString("%1 %2 %3").arg(stats.m_mad[0]).arg(stats.m_mad[1]).arg(stats.m_mad[2])});
|
|
||||||
info.info.append({QObject::tr("Saturated"), QString("%1 %2 %3%").arg(100.0 * stats.m_saturated[0] / rawImage->size())
|
|
||||||
.arg(100.0 * stats.m_saturated[1] / rawImage->size())
|
|
||||||
.arg(100.0 * stats.m_saturated[2] / rawImage->size())});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(m_thumbnail)
|
|
||||||
{
|
|
||||||
if(rawImage && rawImage->valid())
|
|
||||||
{
|
|
||||||
if(QUALITY_RESIZE)
|
|
||||||
rawImage->resize(THUMB_SIZE, THUMB_SIZE);
|
|
||||||
|
|
||||||
rawImage->convertToGLFormat();
|
|
||||||
rawImage->convertToThumbnail();
|
|
||||||
}
|
|
||||||
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
QMetaObject::invokeMethod(m_receiver, "imageLoaded", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage), Q_ARG(ImageInfoData, info));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(std::exception e)
|
|
||||||
{
|
|
||||||
qDebug() << m_file << e.what();
|
|
||||||
std::shared_ptr<RawImage> rawImage;
|
|
||||||
if(m_thumbnail)
|
|
||||||
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage));
|
|
||||||
else
|
|
||||||
QMetaObject::invokeMethod(m_receiver, "imageLoaded", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage), Q_ARG(ImageInfoData, ImageInfoData()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool readFITSHeader(const QString &path, ImageInfoData &info)
|
|
||||||
{
|
|
||||||
fitsfile *fr;
|
|
||||||
int status = 0;
|
|
||||||
QString path2 = makeMaxPath(path);
|
|
||||||
fits_open_diskfile(&fr, path2.toLocal8Bit().data(), READONLY, &status);
|
|
||||||
|
|
||||||
if(fr && status == 0)
|
|
||||||
{
|
|
||||||
status = loadFITSHeader(fr, info);
|
|
||||||
fits_close_file(fr, &status);
|
|
||||||
}
|
|
||||||
return status == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool readXISFHeader(const QString &path, ImageInfoData &info)
|
|
||||||
{
|
|
||||||
QString path2 = makeMaxPath(path);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LibXISF::XISFReader xisf;
|
|
||||||
xisf.open(path2.toLocal8Bit().data());
|
|
||||||
const LibXISF::Image &image = xisf.getImage(0, false);
|
|
||||||
|
|
||||||
auto fitskeywords = image.fitsKeywords();
|
|
||||||
for(auto fits : fitskeywords)
|
|
||||||
{
|
|
||||||
info.fitsHeader.append(fits);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto imageproperties = image.imageProperties();
|
|
||||||
for(auto prop : imageproperties)
|
|
||||||
{
|
|
||||||
info.fitsHeader.append(prop);
|
|
||||||
}
|
|
||||||
info.wcs = std::make_shared<WCSDataT>(image.width(), image.height(), info.fitsHeader);
|
|
||||||
if(!info.wcs->valid())info.wcs.reset();
|
|
||||||
}
|
|
||||||
catch (LibXISF::Error &err)
|
|
||||||
{
|
|
||||||
qDebug() << err.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar)
|
|
||||||
{
|
|
||||||
bool ret = false;
|
|
||||||
QElapsedTimer timer;
|
|
||||||
timer.start();
|
|
||||||
if(path.endsWith(".CR2", Qt::CaseInsensitive) || path.endsWith(".CR3", Qt::CaseInsensitive) || path.endsWith(".NEF", Qt::CaseInsensitive) || path.endsWith(".DNG", Qt::CaseInsensitive))
|
|
||||||
{
|
|
||||||
ret = loadRAW(path, info, rawImage);
|
|
||||||
qDebug() << "LoadRAW" << timer.elapsed();
|
|
||||||
}
|
|
||||||
else if(path.endsWith(".FIT", Qt::CaseInsensitive) || path.endsWith(".FITS", Qt::CaseInsensitive) || path.endsWith(".FZ", Qt::CaseInsensitive) || path.endsWith(".FTS", Qt::CaseInsensitive))
|
|
||||||
{
|
|
||||||
ret = loadFITS(path, info, rawImage, planar);
|
|
||||||
qDebug() << "LoadFITS" << timer.elapsed();
|
|
||||||
}
|
|
||||||
else if(path.endsWith(".XISF", Qt::CaseInsensitive))
|
|
||||||
{
|
|
||||||
ret = loadXISF(path, info, rawImage, planar);
|
|
||||||
qDebug() << "LoadXISF" << timer.elapsed();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
QImage img(path);
|
|
||||||
|
|
||||||
ExifData *exif = exif_data_new_from_file(path.toLocal8Bit().constData());
|
|
||||||
info.info.append({QObject::tr("Width"), QString::number(img.width())});
|
|
||||||
info.info.append({QObject::tr("Height"), QString::number(img.height())});
|
|
||||||
if(exif)
|
|
||||||
{
|
|
||||||
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_ISO_SPEED_RATINGS);
|
|
||||||
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_SHUTTER_SPEED_VALUE);
|
|
||||||
exif_data_free(exif);
|
|
||||||
}
|
|
||||||
rawImage = std::make_shared<RawImage>(img);
|
|
||||||
qDebug() << "LoadQImage" << timer.elapsed();
|
|
||||||
ret = !img.isNull();
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConvertRunable::ConvertRunable(const QString &in, const QString &out, const QString &format, const ConvertParams ¶ms, QSemaphore *semaphore) :
|
|
||||||
m_infile(makeMaxPath(in)),
|
|
||||||
m_outfile(makeMaxPath(out)),
|
|
||||||
m_format(format),
|
|
||||||
m_params(params),
|
|
||||||
m_semaphore(semaphore)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void writeFITSImage(fitsfile *fw, std::shared_ptr<RawImage> rawimage, ImageInfoData &imageinfo)
|
|
||||||
{
|
|
||||||
static QStringList skipKeys = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "BZERO", "BSCALE", "EXTEND"};
|
|
||||||
|
|
||||||
int status = 0;
|
|
||||||
long firstpix[3] = {1,1,1};
|
|
||||||
|
|
||||||
int channels = rawimage->channels();
|
|
||||||
int naxis = channels == 1 ? 2 : 3;
|
|
||||||
long naxes[3] = {(int)rawimage->width(), (int)rawimage->height(), rawimage->channels()};
|
|
||||||
|
|
||||||
std::vector<RawImage> planes;
|
|
||||||
if(channels == 1)
|
|
||||||
planes.push_back(*rawimage);
|
|
||||||
else
|
|
||||||
planes = rawimage->split();
|
|
||||||
|
|
||||||
switch(rawimage->type())
|
|
||||||
{
|
|
||||||
case RawImage::UINT8:
|
|
||||||
fits_create_img(fw, BYTE_IMG, naxis, naxes, &status);
|
|
||||||
for(int i=0; i<channels; i++)
|
|
||||||
{
|
|
||||||
firstpix[2] = i+1;
|
|
||||||
fits_write_pix(fw, TBYTE, firstpix, rawimage->size(), planes[i].data(), &status);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RawImage::UINT16:
|
|
||||||
fits_create_img(fw, USHORT_IMG, naxis, naxes, &status);
|
|
||||||
for(int i=0; i<channels; i++)
|
|
||||||
{
|
|
||||||
firstpix[2] = i+1;
|
|
||||||
fits_write_pix(fw, TUSHORT, firstpix, rawimage->size(), planes[i].data(), &status);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RawImage::FLOAT32:
|
|
||||||
fits_create_img(fw, FLOAT_IMG, naxis, naxes, &status);
|
|
||||||
for(int i=0; i<channels; i++)
|
|
||||||
{
|
|
||||||
firstpix[2] = i+1;
|
|
||||||
fits_write_pix(fw, TFLOAT, firstpix, rawimage->size(), planes[i].data(), &status);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for(const FITSRecord &record : imageinfo.fitsHeader)
|
|
||||||
{
|
|
||||||
if(skipKeys.contains(record.key) || record.xisf)continue;
|
|
||||||
|
|
||||||
bool isdouble;
|
|
||||||
bool isint;
|
|
||||||
bool isbool = record.value.toString() == "T" || record.value.toString() == "F";
|
|
||||||
double vald = record.value.toDouble(&isdouble);
|
|
||||||
int valb = record.value.toString() == "T";
|
|
||||||
long long vall = record.value.toLongLong(&isint);
|
|
||||||
QByteArray str = record.value.toString().toLatin1();
|
|
||||||
if(isdouble)
|
|
||||||
fits_write_key(fw, TDOUBLE, record.key.data(), &vald, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
|
||||||
else if(isint)
|
|
||||||
fits_write_key(fw, TLONGLONG, record.key.data(), &vall, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
|
||||||
else if(isbool)
|
|
||||||
fits_write_key(fw, TLOGICAL, record.key.data(), &valb, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
|
||||||
else if(record.key == "COMMENT")
|
|
||||||
fits_write_comment(fw, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
|
||||||
else if(record.key == "HISTORY")
|
|
||||||
fits_write_history(fw, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
|
||||||
else
|
|
||||||
fits_write_key(fw, TSTRING, record.key.data(), str.isEmpty() ? nullptr : str.data(), record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConvertRunable::run()
|
|
||||||
{
|
|
||||||
QSemaphoreReleaser release;
|
|
||||||
if(m_semaphore)release = QSemaphoreReleaser(m_semaphore);
|
|
||||||
|
|
||||||
ImageInfoData imageinfo;
|
|
||||||
std::shared_ptr<RawImage> rawimage;
|
|
||||||
loadImage(m_infile, imageinfo, rawimage);
|
|
||||||
QFileInfo info(m_outfile);
|
|
||||||
info.dir().mkpath(".");
|
|
||||||
|
|
||||||
if(rawimage)
|
|
||||||
{
|
|
||||||
if(m_format == "xisf")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LibXISF::XISFWriter xisf;
|
|
||||||
int channelCount = rawimage->channels();
|
|
||||||
LibXISF::Image::SampleFormat sampleFormat;
|
|
||||||
switch(rawimage->type())
|
|
||||||
{
|
|
||||||
case RawImage::UINT8: sampleFormat = LibXISF::Image::UInt8; break;
|
|
||||||
case RawImage::UINT16: sampleFormat = LibXISF::Image::UInt16; break;
|
|
||||||
case RawImage::FLOAT32: sampleFormat = LibXISF::Image::Float32; break;
|
|
||||||
default: return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LibXISF::Image image(rawimage->width(), rawimage->height(), channelCount, sampleFormat, channelCount == 1 ? LibXISF::Image::Gray : LibXISF::Image::RGB, LibXISF::Image::Planar);
|
|
||||||
if(channelCount == 1)
|
|
||||||
{
|
|
||||||
std::memcpy(image.imageData(), rawimage->data(), image.imageDataSize());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
size_t off = 0;
|
|
||||||
std::vector<RawImage> planes = rawimage->split();
|
|
||||||
for(const auto &plane : planes)
|
|
||||||
{
|
|
||||||
std::memcpy(image.imageData<uint8_t>() + off, plane.data(), plane.size() * RawImage::typeSize(plane.type()));
|
|
||||||
off += plane.size() * RawImage::typeSize(plane.type());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(auto &record : imageinfo.fitsHeader)
|
|
||||||
{
|
|
||||||
if(record.xisf)continue;
|
|
||||||
|
|
||||||
if(record.value.typeId() == QMetaType::Bool)
|
|
||||||
image.addFITSKeyword({record.key.toStdString(), record.value.toBool() ? "T" : "F", record.comment.toStdString()});
|
|
||||||
else
|
|
||||||
image.addFITSKeyword({record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(m_params.compressionType.startsWith("zstd") && LibXISF::DataBlock::CompressionCodecSupported(LibXISF::DataBlock::ZSTD))
|
|
||||||
image.setCompression(LibXISF::DataBlock::ZSTD, m_params.compressionLevel);
|
|
||||||
else if(m_params.compressionType.startsWith("lz4hc"))
|
|
||||||
image.setCompression(LibXISF::DataBlock::LZ4HC, m_params.compressionLevel);
|
|
||||||
else if(m_params.compressionType.startsWith("lz4"))
|
|
||||||
image.setCompression(LibXISF::DataBlock::LZ4, m_params.compressionLevel);
|
|
||||||
else if(m_params.compressionType.startsWith("zlib"))
|
|
||||||
image.setCompression(LibXISF::DataBlock::Zlib, m_params.compressionLevel);
|
|
||||||
|
|
||||||
if(m_params.compressionType.endsWith("+sh"))
|
|
||||||
image.setByteshuffling(true);
|
|
||||||
|
|
||||||
xisf.writeImage(image);
|
|
||||||
xisf.save(m_outfile.toLocal8Bit().data());
|
|
||||||
}
|
|
||||||
catch(LibXISF::Error &err)
|
|
||||||
{
|
|
||||||
qDebug() << "Failed to save XISF image" << err.what();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(m_format == "fits")
|
|
||||||
{
|
|
||||||
int status = 0;
|
|
||||||
fitsfile *fw;
|
|
||||||
if(QFileInfo(m_outfile).exists())QFile::remove(m_outfile);
|
|
||||||
fits_create_diskfile(&fw, m_outfile.toLocal8Bit().data(), &status);
|
|
||||||
if(!m_params.compressionType.isEmpty())
|
|
||||||
{
|
|
||||||
if(m_params.compressionType == "gzip")
|
|
||||||
fits_set_compression_type(fw, GZIP_1, &status);
|
|
||||||
else if(m_params.compressionType == "rice")
|
|
||||||
fits_set_compression_type(fw, RICE_1, &status);
|
|
||||||
}
|
|
||||||
writeFITSImage(fw, rawimage, imageinfo);
|
|
||||||
fits_close_file(fw, &status);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if nothing else try QImage
|
|
||||||
{
|
|
||||||
QImage::Format format = QImage::Format_Invalid;
|
|
||||||
int width = rawimage->widthBytes();
|
|
||||||
|
|
||||||
switch(rawimage->type())
|
|
||||||
{
|
|
||||||
case RawImage::UINT8:
|
|
||||||
if(rawimage->channels() == 1)format = QImage::Format_Grayscale8;
|
|
||||||
else if(rawimage->channels() == 3)format = QImage::Format_RGBX8888;
|
|
||||||
else if(rawimage->channels() == 4)format = QImage::Format_RGBA8888;
|
|
||||||
break;
|
|
||||||
case RawImage::UINT16:
|
|
||||||
if(rawimage->channels() == 1)format = QImage::Format_Grayscale16;
|
|
||||||
else if(rawimage->channels() == 3)format = QImage::Format_RGBX64;
|
|
||||||
else if(rawimage->channels() == 4)format = QImage::Format_RGBA64;
|
|
||||||
width *= 2;
|
|
||||||
break;
|
|
||||||
case RawImage::FLOAT16:
|
|
||||||
case RawImage::FLOAT32:
|
|
||||||
case RawImage::FLOAT64:
|
|
||||||
case RawImage::UINT32:
|
|
||||||
rawimage->convertToType(RawImage::UINT16);
|
|
||||||
if(rawimage->channels() == 1)format = QImage::Format_Grayscale16;
|
|
||||||
else if(rawimage->channels() == 3)format = QImage::Format_RGBX64;
|
|
||||||
else if(rawimage->channels() == 4)format = QImage::Format_RGBA64;
|
|
||||||
width *= 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(format == QImage::Format_Invalid)return;
|
|
||||||
|
|
||||||
QImage qimage(rawimage->width(), rawimage->height(), format);
|
|
||||||
for(uint32_t i=0; i < rawimage->height(); i++)
|
|
||||||
std::memcpy(qimage.scanLine(i), rawimage->data(i), width);
|
|
||||||
qimage.save(m_outfile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ConvertRunable::ConvertParams::ConvertParams(const QVariantMap &map)
|
|
||||||
{
|
|
||||||
bool ok = false;
|
|
||||||
if(map.contains("compressionLevel"))
|
|
||||||
compressionLevel = std::clamp(map["compressionLevel"].toInt(&ok), -1, 100);
|
|
||||||
|
|
||||||
if(!ok)compressionLevel = -1;
|
|
||||||
|
|
||||||
if(map.contains("compressionType"))
|
|
||||||
compressionType = map["compressionType"].toString();
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#include "mainwindow.h"
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QSurfaceFormat>
|
|
||||||
#include <QTranslator>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
#ifdef __linux__
|
|
||||||
setenv("LC_NUMERIC", "C", 1);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(__i386__) || defined(__x86_64__) || defined(__APPLE__)
|
|
||||||
bool useGLES = false;
|
|
||||||
#else
|
|
||||||
bool useGLES = true;
|
|
||||||
#endif
|
|
||||||
for(int i = 0; i < argc; i++)
|
|
||||||
{
|
|
||||||
if(std::strcmp("-gl", argv[i]) == 0)
|
|
||||||
useGLES = false;
|
|
||||||
if(std::strcmp("-gles", argv[i]) == 0)
|
|
||||||
useGLES = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSurfaceFormat format;
|
|
||||||
if(useGLES)
|
|
||||||
{
|
|
||||||
format.setMajorVersion(3);
|
|
||||||
format.setMinorVersion(0);
|
|
||||||
format.setRenderableType(QSurfaceFormat::OpenGLES);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
format.setMajorVersion(3);
|
|
||||||
format.setMinorVersion(3);
|
|
||||||
//format.setOption(QSurfaceFormat::DebugContext);
|
|
||||||
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
|
||||||
}
|
|
||||||
QSurfaceFormat::setDefaultFormat(format);
|
|
||||||
|
|
||||||
QApplication a(argc, argv);
|
|
||||||
a.setOrganizationName("nou");
|
|
||||||
a.setApplicationName("Tenmon");
|
|
||||||
a.setWindowIcon(QIcon(":/space.nouspiro.tenmon.png"));
|
|
||||||
|
|
||||||
QTranslator translator;
|
|
||||||
QTranslator translator2;
|
|
||||||
if(translator.load(QLocale(), "tenmon", "_", ":/translations"))
|
|
||||||
a.installTranslator(&translator);
|
|
||||||
if(translator2.load(QLocale(), "tenmon", "_", a.applicationDirPath()))
|
|
||||||
a.installTranslator(&translator2);
|
|
||||||
|
|
||||||
MainWindow w;
|
|
||||||
w.show();
|
|
||||||
|
|
||||||
return a.exec();
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,141 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.699999 12.699999"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="grid.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="9.5144352"
|
||||||
|
inkscape:cx="39.361243"
|
||||||
|
inkscape:cy="25.067174"
|
||||||
|
inkscape:window-width="2510"
|
||||||
|
inkscape:window-height="1371"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<inkscape:path-effect
|
||||||
|
effect="skeletal"
|
||||||
|
id="path-effect636"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
|
||||||
|
copytype="single_stretched"
|
||||||
|
prop_scale="1"
|
||||||
|
scale_y_rel="false"
|
||||||
|
spacing="0"
|
||||||
|
normal_offset="0"
|
||||||
|
tang_offset="0"
|
||||||
|
prop_units="false"
|
||||||
|
vertical_pattern="false"
|
||||||
|
hide_knot="false"
|
||||||
|
fuse_tolerance="0" />
|
||||||
|
<inkscape:path-effect
|
||||||
|
effect="skeletal"
|
||||||
|
id="path-effect632"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
|
||||||
|
copytype="single_stretched"
|
||||||
|
prop_scale="1"
|
||||||
|
scale_y_rel="false"
|
||||||
|
spacing="0"
|
||||||
|
normal_offset="0"
|
||||||
|
tang_offset="0"
|
||||||
|
prop_units="false"
|
||||||
|
vertical_pattern="false"
|
||||||
|
hide_knot="false"
|
||||||
|
fuse_tolerance="0" />
|
||||||
|
<inkscape:path-effect
|
||||||
|
effect="skeletal"
|
||||||
|
id="path-effect628"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
|
||||||
|
copytype="single_stretched"
|
||||||
|
prop_scale="1"
|
||||||
|
scale_y_rel="false"
|
||||||
|
spacing="0"
|
||||||
|
normal_offset="0"
|
||||||
|
tang_offset="0"
|
||||||
|
prop_units="false"
|
||||||
|
vertical_pattern="false"
|
||||||
|
hide_knot="false"
|
||||||
|
fuse_tolerance="0" />
|
||||||
|
<inkscape:path-effect
|
||||||
|
effect="bspline"
|
||||||
|
id="path-effect624"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
weight="33.333333"
|
||||||
|
steps="2"
|
||||||
|
helper_size="0"
|
||||||
|
apply_no_weight="true"
|
||||||
|
apply_with_weight="true"
|
||||||
|
only_selected="false" />
|
||||||
|
<inkscape:path-effect
|
||||||
|
effect="spiro"
|
||||||
|
id="path-effect620"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Vrstva 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
d="M -5,-5 13,13"
|
||||||
|
id="path616"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
id="path643"
|
||||||
|
cx="-4.9824347"
|
||||||
|
cy="-4.9865055"
|
||||||
|
r="12.973718" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1665"
|
||||||
|
cx="-4.9600825"
|
||||||
|
cy="-4.9741392"
|
||||||
|
r="17.086035" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1667"
|
||||||
|
cx="-5.0079365"
|
||||||
|
cy="-5.0034046"
|
||||||
|
r="21.147657" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.467;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
d="M 14.371451,3.5622727 -4.9904999,-5.0054782 4.2432806,13.903978"
|
||||||
|
id="path1734" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1736"
|
||||||
|
cx="-5.155458"
|
||||||
|
cy="-5.1256938"
|
||||||
|
r="9.6808758" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
@@ -16,6 +16,11 @@
|
|||||||
<file>grbg.png</file>
|
<file>grbg.png</file>
|
||||||
<file>gbrg.png</file>
|
<file>gbrg.png</file>
|
||||||
<file>space.nouspiro.tenmon.png</file>
|
<file>space.nouspiro.tenmon.png</file>
|
||||||
|
<file>../translations/tenmon_pt_BR.qm</file>
|
||||||
|
<file alias="help">../about/help_en</file>
|
||||||
|
<file>colormap.png</file>
|
||||||
|
<file>ngc.db</file>
|
||||||
|
<file>grid.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource lang="en" prefix="/">
|
<qresource lang="en" prefix="/">
|
||||||
<file alias="help">../about/help_en</file>
|
<file alias="help">../about/help_en</file>
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
core.log("This script convert any FITS file into XISF with ZSTD compression");
|
core.log("This script convert any FITS file into XISF with ZSTD compression");
|
||||||
|
|
||||||
if(files.length == 0)
|
|
||||||
{
|
|
||||||
core.log("No input files");
|
|
||||||
throw "";
|
|
||||||
}
|
|
||||||
|
|
||||||
let compression = {"compressionType": "zstd+sh"};
|
let compression = {"compressionType": "zstd+sh"};
|
||||||
|
|
||||||
for(file of files)
|
for(file of files)
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
core.log("Measure HFR and eccentricity of stars");
|
||||||
|
|
||||||
|
var chart = {
|
||||||
|
"title": "Measure stars",
|
||||||
|
"legend": {"visible": true, "align": "left"},
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"title": "HFR",
|
||||||
|
"type": "bar",
|
||||||
|
"y":[]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ecc",
|
||||||
|
"type": "bar",
|
||||||
|
"y":[]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Star count",
|
||||||
|
"type": "linePoints",
|
||||||
|
"y":[],
|
||||||
|
"y2": true,
|
||||||
|
"bestFit": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
core.setSolverProfile(5);
|
||||||
|
for(file of files)
|
||||||
|
{
|
||||||
|
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
|
||||||
|
{
|
||||||
|
var stars = file.extractStars(true);
|
||||||
|
var sumHFR = 0;
|
||||||
|
var ecc = 0;
|
||||||
|
for(star of stars)
|
||||||
|
{
|
||||||
|
sumHFR += star.HFR;
|
||||||
|
ecc += Math.sqrt(1 - (star.b * star.b) / (star.a * star.a));
|
||||||
|
}
|
||||||
|
chart.series[0].y.push(sumHFR / stars.length);
|
||||||
|
chart.series[1].y.push(ecc / stars.length);
|
||||||
|
chart.series[2].y.push(stars.length);
|
||||||
|
|
||||||
|
core.log(file.fileName() + " Stars:" + stars.length + " HFR: " + sumHFR / stars.length + " Ecc: " + ecc / stars.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.plot(chart);
|
||||||
|
|
||||||
+47
-12
@@ -1,3 +1,5 @@
|
|||||||
|
core.log("Script to modify FITS header in FITS and XISF files");
|
||||||
|
|
||||||
function checkFITS(key)
|
function checkFITS(key)
|
||||||
{
|
{
|
||||||
const noEditableKey = ["SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"];
|
const noEditableKey = ["SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"];
|
||||||
@@ -10,10 +12,12 @@ if(files.length == 0)
|
|||||||
throw "";
|
throw "";
|
||||||
}
|
}
|
||||||
|
|
||||||
let action = core.getItem(["UPDATE", "ADD", "REMOVE"], "Do you want update, add or remove record?");
|
let action = core.getItem(["UPDATE", "UPDATE_ADD", "ADD", "REMOVE"], "Do you want update, add or remove record?");
|
||||||
|
|
||||||
let modify = new FITSRecordModify();
|
let modify = new FITSRecordModify();
|
||||||
|
|
||||||
|
let proceed = false;
|
||||||
|
|
||||||
if(action == "UPDATE")
|
if(action == "UPDATE")
|
||||||
{
|
{
|
||||||
let keywords = files[0].fitsKeywords().filter(checkFITS);
|
let keywords = files[0].fitsKeywords().filter(checkFITS);
|
||||||
@@ -23,28 +27,59 @@ if(action == "UPDATE")
|
|||||||
value = core.getString("Enter new value", value);
|
value = core.getString("Enter new value", value);
|
||||||
else
|
else
|
||||||
value = core.getFloat("Enter new value", value);
|
value = core.getFloat("Enter new value", value);
|
||||||
modify.updateKeyword(keyword, value);
|
|
||||||
|
if(keyword && value)
|
||||||
|
{
|
||||||
|
proceed = true;
|
||||||
|
modify.updateKeyword(keyword, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(action == "UPDATE_ADD")
|
||||||
|
{
|
||||||
|
let keyword = core.getString("Enter keyword to update");
|
||||||
|
let value = core.getString("Enter new value");
|
||||||
|
if(keyword && value)
|
||||||
|
{
|
||||||
|
proceed = true;
|
||||||
|
keyword = keyword.toUpperCase();
|
||||||
|
modify.updateKeyword(keyword, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(action == "ADD")
|
else if(action == "ADD")
|
||||||
{
|
{
|
||||||
let keyword = core.getString("Enter keyword to add");
|
let keyword = core.getString("Enter keyword to add");
|
||||||
let value = core.getString("Enter new value");
|
let value = core.getString("Enter new value");
|
||||||
keyword = keyword.toUpperCase();
|
if(keyword && value)
|
||||||
modify.addKeyword(keyword, value);
|
{
|
||||||
|
proceed = true;
|
||||||
|
keyword = keyword.toUpperCase();
|
||||||
|
modify.addKeyword(keyword, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(action == "REMOVE")
|
else if(action == "REMOVE")
|
||||||
{
|
{
|
||||||
let keywords = files[0].fitsKeywords().filter(checkFITS);
|
let keywords = files[0].fitsKeywords().filter(checkFITS);
|
||||||
let keyword = core.getItem(keywords, "Select keyword to remove");
|
let keyword = core.getItem(keywords, "Select keyword to remove");
|
||||||
modify.removeKeyword(keyword);
|
if(keyword)
|
||||||
}
|
|
||||||
|
|
||||||
for(file of files)
|
|
||||||
{
|
|
||||||
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
|
|
||||||
{
|
{
|
||||||
core.log("Modifing " + file.fileName());
|
proceed = true;
|
||||||
file.modifyFITSRecords(modify);
|
modify.removeKeyword(keyword);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(proceed)
|
||||||
|
{
|
||||||
|
for(file of files)
|
||||||
|
{
|
||||||
|
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
|
||||||
|
{
|
||||||
|
core.log("Modifing " + file.fileName());
|
||||||
|
file.modifyFITSRecords(modify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
core.log("Canceled");
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
core.log("Plate solve and update solution");
|
||||||
|
|
||||||
|
var first = true;
|
||||||
|
|
||||||
|
var update = core.question("Update FITS header with solution?", ["yes", "no"], "Update FITS header") == "yes";
|
||||||
|
var blind = core.question("Do blind solve every image?", ["yes", "no"], "Blind solve?") == "yes";
|
||||||
|
|
||||||
|
for(file of files)
|
||||||
|
{
|
||||||
|
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
|
||||||
|
{
|
||||||
|
var solution = file.solve(update);
|
||||||
|
if(first && !blind)
|
||||||
|
{
|
||||||
|
core.setStartingSolution(solution);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
core.log(file.fileName() + " " + "RA: " + (solution.ra / 15) + "h DEC: " + solution.dec + "deg");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,5 +4,7 @@
|
|||||||
<file>convert to XISF</file>
|
<file>convert to XISF</file>
|
||||||
<file>median</file>
|
<file>median</file>
|
||||||
<file>modify FITS header</file>
|
<file>modify FITS header</file>
|
||||||
|
<file>measure HFR</file>
|
||||||
|
<file>plate solve</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
+5
-11
@@ -1,5 +1,6 @@
|
|||||||
uniform sampler2D qt_Texture0;
|
uniform sampler2D qt_Texture0;
|
||||||
uniform sampler3D lut_table;
|
uniform sampler3D lut_table;
|
||||||
|
uniform sampler2DArray colormap;
|
||||||
uniform vec3 mtf_param[3];
|
uniform vec3 mtf_param[3];
|
||||||
uniform vec2 unit_scale;
|
uniform vec2 unit_scale;
|
||||||
uniform bool bw;
|
uniform bool bw;
|
||||||
@@ -7,6 +8,7 @@ uniform bool invert;
|
|||||||
uniform bool srgb;
|
uniform bool srgb;
|
||||||
uniform bool false_color;
|
uniform bool false_color;
|
||||||
uniform int filtering;
|
uniform int filtering;
|
||||||
|
uniform int colormapIdx;
|
||||||
in vec2 qt_TexCoord0;
|
in vec2 qt_TexCoord0;
|
||||||
layout(location = 0) out vec4 color;
|
layout(location = 0) out vec4 color;
|
||||||
|
|
||||||
@@ -26,17 +28,9 @@ vec4 MTF(vec4 x, vec4 low, vec4 mid, vec4 high)
|
|||||||
|
|
||||||
vec3 falsecolor(float color)
|
vec3 falsecolor(float color)
|
||||||
{
|
{
|
||||||
const vec3 pallete[] = vec3[](
|
color *= 255.0 / 256.0;
|
||||||
vec3(1.0, 0.0, 1.0), //magneta
|
color += 0.5 / 256.0;
|
||||||
vec3(0.0, 0.0, 1.0), //blue
|
return texture(colormap, vec3(color, 0.5, colormapIdx)).rgb;
|
||||||
vec3(0.0, 1.0, 1.0), //cyan
|
|
||||||
vec3(0.0, 1.0, 0.0), //green
|
|
||||||
vec3(1.0, 1.0, 0.0), //yellow
|
|
||||||
vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0));//red
|
|
||||||
color *= 5.0;
|
|
||||||
int i = int(color);
|
|
||||||
float f = fract(color);
|
|
||||||
return mix(pallete[i], pallete[i+1], f);// * (f * 0.5 + 0.5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 checker()
|
vec3 checker()
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
<li>Convert CFA images to colour - debayer</li>
|
<li>Convert CFA images to colour - debayer</li>
|
||||||
<li>Color space aware</li>
|
<li>Color space aware</li>
|
||||||
<li>Histogram</li>
|
<li>Histogram</li>
|
||||||
|
<li>Scripting</li>
|
||||||
|
<li>Plate solving</li>
|
||||||
</ul>
|
</ul>
|
||||||
</description>
|
</description>
|
||||||
<categories>
|
<categories>
|
||||||
@@ -45,6 +47,7 @@
|
|||||||
</keywords>
|
</keywords>
|
||||||
<url type="homepage">https://nouspiro.space/?page_id=206</url>
|
<url type="homepage">https://nouspiro.space/?page_id=206</url>
|
||||||
<url type="bugtracker">https://github.com/flathub/space.nouspiro.tenmon/issues</url>
|
<url type="bugtracker">https://github.com/flathub/space.nouspiro.tenmon/issues</url>
|
||||||
|
<url type="vcs-browser">https://gitea.nouspiro.space/nou/tenmon</url>
|
||||||
<screenshots>
|
<screenshots>
|
||||||
<screenshot type="default">
|
<screenshot type="default">
|
||||||
<caption>Main window with image</caption>
|
<caption>Main window with image</caption>
|
||||||
@@ -57,6 +60,64 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
<content_rating type="oars-1.1"/>
|
<content_rating type="oars-1.1"/>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="20260217" date="2026-02-17">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Fix potentional crash</li>
|
||||||
|
<li>Enable sorting of FITS info</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="20251101" date="2025-11-01">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Better image Save as</li>
|
||||||
|
<li>Fix xisf file corruption when platesolving</li>
|
||||||
|
<li>Add selecting language</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="20250915" date="2025-09-15">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Draw equatorial grid and objects overlay</li>
|
||||||
|
<li>File Manager</li>
|
||||||
|
<li>Support for PCL:AstrometricSolution</li>
|
||||||
|
<li>Script console</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="20250429" date="2025-04-29">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Add ability to load multiple images in single file</li>
|
||||||
|
<li>New plot() and question() script methods</li>
|
||||||
|
<li>Color highlight of FITS keywords</li>
|
||||||
|
<li>New scripts to batch platesolve and measure stars</li>
|
||||||
|
<li>Stretch toolbar can now be vertical</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="20250318" date="2025-03-18">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Fix OpenGL ES drawings</li>
|
||||||
|
<li>Fix mark/unmark files from script</li>
|
||||||
|
<li>Fix stretching of float images with values outside of 0-1 range</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="20250302" date="2025-03-02">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Add resize and binning to script</li>
|
||||||
|
<li>Auto stretch to script</li>
|
||||||
|
<li>Fix opening UNC paths starting</li>
|
||||||
|
<li>Add more color maps for false color</li>
|
||||||
|
<li>Open image with best fit</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="20250126" date="2025-01-26">
|
<release version="20250126" date="2025-01-26">
|
||||||
<description>
|
<description>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -86,7 +147,7 @@
|
|||||||
</release>
|
</release>
|
||||||
<release version="20240816" date="2024-08-16">
|
<release version="20240816" date="2024-08-16">
|
||||||
<description>
|
<description>
|
||||||
Fix saving image
|
<p>Fix saving image</p>
|
||||||
</description>
|
</description>
|
||||||
</release>
|
</release>
|
||||||
<release version="20240616" date="2024-06-16">
|
<release version="20240616" date="2024-06-16">
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||||
|
<mime-type type="image/x-xisf">
|
||||||
|
<comment>Extensible Image Serialization Format</comment>
|
||||||
|
<glob pattern="*.xisf"/>
|
||||||
|
</mime-type>
|
||||||
|
</mime-info>
|
||||||
+13
-13
@@ -15,11 +15,13 @@ About::About(QWidget *parent) : QDialog(parent)
|
|||||||
QLabel *label = new QLabel(this);
|
QLabel *label = new QLabel(this);
|
||||||
|
|
||||||
QFile tenmonText(":/about/tenmon");
|
QFile tenmonText(":/about/tenmon");
|
||||||
tenmonText.open(QIODevice::ReadOnly);
|
if(tenmonText.open(QIODevice::ReadOnly))
|
||||||
QByteArray text = tenmonText.readAll();
|
{
|
||||||
text.replace("@GITVERSION@", GITVERSION);
|
QByteArray text = tenmonText.readAll();
|
||||||
label->setText(text);
|
text.replace("@GITVERSION@", GITVERSION);
|
||||||
label->setOpenExternalLinks(true);
|
label->setText(text);
|
||||||
|
label->setOpenExternalLinks(true);
|
||||||
|
}
|
||||||
|
|
||||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
|
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
@@ -31,20 +33,18 @@ About::About(QWidget *parent) : QDialog(parent)
|
|||||||
HelpDialog::HelpDialog(QWidget *parent) : QDialog(parent)
|
HelpDialog::HelpDialog(QWidget *parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
setWindowTitle(tr("Help"));
|
setWindowTitle(tr("Help"));
|
||||||
resize(800, 600);
|
resize(1000, 600);
|
||||||
|
setModal(false);
|
||||||
QLocale locale;
|
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||||
QString l = QLocale::languageToString(locale.language());
|
|
||||||
|
|
||||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||||
QTextEdit *helpText = new QTextEdit(this);
|
QTextEdit *helpText = new QTextEdit(this);
|
||||||
helpText->setReadOnly(true);
|
helpText->setReadOnly(true);
|
||||||
|
layout->addWidget(helpText);
|
||||||
|
|
||||||
QFile tenmonText(":/help");
|
QFile tenmonText(":/help");
|
||||||
tenmonText.open(QIODevice::ReadOnly);
|
if(tenmonText.open(QIODevice::ReadOnly))
|
||||||
helpText->setHtml(tenmonText.readAll());
|
helpText->setHtml(tenmonText.readAll());
|
||||||
|
|
||||||
layout->addWidget(helpText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString getVersion()
|
QString getVersion()
|
||||||
@@ -10,10 +10,14 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
|
#include <QChart>
|
||||||
|
#include <QChartView>
|
||||||
|
#include <QLineSeries>
|
||||||
|
#include <QCompleter>
|
||||||
#include "scriptengine.h"
|
#include "scriptengine.h"
|
||||||
|
#include "chartgraph.h"
|
||||||
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
#include <QCloseEvent>
|
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
#endif
|
#endif
|
||||||
@@ -67,7 +71,8 @@ void BatchProcessing::scanScriptDir()
|
|||||||
if(idx>=0)_ui->scriptsList->setCurrentRow(idx);
|
if(idx>=0)_ui->scriptsList->setCurrentRow(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
BatchProcessing::BatchProcessing(QWidget *parent) : QDialog(parent)
|
BatchProcessing::BatchProcessing(Database *database, QWidget *parent) : QDialog(parent)
|
||||||
|
, _database(database)
|
||||||
{
|
{
|
||||||
_ui = new Ui::BatchProcessing;
|
_ui = new Ui::BatchProcessing;
|
||||||
_ui->setupUi(this);
|
_ui->setupUi(this);
|
||||||
@@ -93,17 +98,43 @@ BatchProcessing::BatchProcessing(QWidget *parent) : QDialog(parent)
|
|||||||
qWarning() << "Failed to get app data location";
|
qWarning() << "Failed to get app data location";
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(_ui->addFilesButton, &QPushButton::released, this, &BatchProcessing::addFiles);
|
connect(_ui->addFilesButton, &QPushButton::released, this, static_cast<void (BatchProcessing::*)()>(&BatchProcessing::addFiles));
|
||||||
connect(_ui->addDirButton, &QPushButton::released, this, &BatchProcessing::addDir);
|
connect(_ui->addDirButton, &QPushButton::released, this, static_cast<void (BatchProcessing::*)()>(&BatchProcessing::addDir));
|
||||||
|
connect(_ui->addMarkedButton, &QPushButton::released, this, &BatchProcessing::addMarked);
|
||||||
connect(_ui->removeButton, &QPushButton::released, this, &BatchProcessing::removePath);
|
connect(_ui->removeButton, &QPushButton::released, this, &BatchProcessing::removePath);
|
||||||
connect(_ui->removeAllButton, &QPushButton::released, this, &BatchProcessing::removeAllPaths);
|
connect(_ui->removeAllButton, &QPushButton::released, this, &BatchProcessing::removeAllPaths);
|
||||||
connect(_ui->startButton, &QPushButton::released, this, &BatchProcessing::runScript);
|
connect(_ui->startButton, &QPushButton::released, this, static_cast<void (BatchProcessing::*)()>(&BatchProcessing::runScript));
|
||||||
connect(_ui->stopButton, &QPushButton::released, this, &BatchProcessing::stopScript);
|
connect(_ui->stopButton, &QPushButton::released, this, &BatchProcessing::stopScript);
|
||||||
connect(_ui->browseButton, &QPushButton::released, this, &BatchProcessing::browse);
|
connect(_ui->browseButton, &QPushButton::released, this, &BatchProcessing::browse);
|
||||||
connect(_ui->openScriptsButton, &QPushButton::released, this, &BatchProcessing::openScriptDir);
|
connect(_ui->openScriptsButton, &QPushButton::released, this, &BatchProcessing::openScriptDir);
|
||||||
|
|
||||||
_textColor = _ui->log->palette().text().color();
|
_textColor = _ui->log->palette().text().color();
|
||||||
|
|
||||||
|
_engine = new Script::ScriptEngine(_database, this);
|
||||||
|
connect(_engine, &Script::ScriptEngine::newMessage, this, &BatchProcessing::newMessage);
|
||||||
|
|
||||||
|
_completerModel = new QStringListModel(this);
|
||||||
|
_completer = new QCompleter(_completerModel, this);
|
||||||
|
_ui->consoleLineEdit->setCompleter(_completer);
|
||||||
|
connect(_ui->executeButton, &QPushButton::clicked, _ui->consoleLineEdit, &QLineEdit::returnPressed);
|
||||||
|
connect(_ui->consoleLineEdit, &QLineEdit::returnPressed, [this](){
|
||||||
|
if(!_completer->popup()->isVisible())
|
||||||
|
{
|
||||||
|
QString program = _ui->consoleLineEdit->text();
|
||||||
|
QJSValue val = _engine->eval(program);
|
||||||
|
_ui->consoleLineEdit->addLine();
|
||||||
|
//qDebug() << val.toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(_ui->consoleLineEdit, &QLineEdit::textEdited, [this](const QString &text){
|
||||||
|
QStringList comp = _engine->complete(text);
|
||||||
|
//qDebug() << comp;
|
||||||
|
_completerModel->setStringList(comp);
|
||||||
|
});
|
||||||
|
|
||||||
|
_ui->addFilesButton->setAutoDefault(false);
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
_ui->outputPath->setText(settings.value("batchprocessing/outputpath", QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).first()).toString());
|
_ui->outputPath->setText(settings.value("batchprocessing/outputpath", QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).first()).toString());
|
||||||
}
|
}
|
||||||
@@ -116,6 +147,17 @@ BatchProcessing::~BatchProcessing()
|
|||||||
delete _ui;
|
delete _ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BatchProcessing::setOutputDir(const QString &output)
|
||||||
|
{
|
||||||
|
_ui->outputPath->setText(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatchProcessing::setPaths(const QStringList &paths)
|
||||||
|
{
|
||||||
|
_ui->pathsList->addItems(paths);
|
||||||
|
refreshPaths();
|
||||||
|
}
|
||||||
|
|
||||||
void BatchProcessing::closeEvent(QCloseEvent *event)
|
void BatchProcessing::closeEvent(QCloseEvent *event)
|
||||||
{
|
{
|
||||||
if(_engineThread)
|
if(_engineThread)
|
||||||
@@ -137,6 +179,15 @@ void BatchProcessing::closeEvent(QCloseEvent *event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BatchProcessing::refreshPaths()
|
||||||
|
{
|
||||||
|
QStringList paths;
|
||||||
|
for(int i=0; i<_ui->pathsList->count(); i++)
|
||||||
|
paths.append(_ui->pathsList->item(i)->text());
|
||||||
|
_paths = scanDirectories(paths);
|
||||||
|
_engine->setParams("", _paths, _ui->outputPath->text(), QString());
|
||||||
|
}
|
||||||
|
|
||||||
void BatchProcessing::addFiles()
|
void BatchProcessing::addFiles()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
@@ -146,6 +197,7 @@ void BatchProcessing::addFiles()
|
|||||||
_ui->pathsList->addItems(files);
|
_ui->pathsList->addItems(files);
|
||||||
settings.setValue("batchprocessing/inputpath", QFileInfo(files.first()).absolutePath());
|
settings.setValue("batchprocessing/inputpath", QFileInfo(files.first()).absolutePath());
|
||||||
}
|
}
|
||||||
|
refreshPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BatchProcessing::addDir()
|
void BatchProcessing::addDir()
|
||||||
@@ -157,17 +209,32 @@ void BatchProcessing::addDir()
|
|||||||
_ui->pathsList->addItem(dir);
|
_ui->pathsList->addItem(dir);
|
||||||
settings.setValue("batchprocessing/inputpath", dir);
|
settings.setValue("batchprocessing/inputpath", dir);
|
||||||
}
|
}
|
||||||
|
refreshPaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatchProcessing::addMarked()
|
||||||
|
{
|
||||||
|
QStringList files = _database->getMarkedFiles();
|
||||||
|
for(const QString &file : files)
|
||||||
|
{
|
||||||
|
QFileInfo info(file);
|
||||||
|
if(info.exists() && info.isReadable())
|
||||||
|
_ui->pathsList->addItem(file);
|
||||||
|
};
|
||||||
|
refreshPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BatchProcessing::removePath()
|
void BatchProcessing::removePath()
|
||||||
{
|
{
|
||||||
for(auto &item : _ui->pathsList->selectedItems())
|
for(auto &item : _ui->pathsList->selectedItems())
|
||||||
delete item;
|
delete item;
|
||||||
|
refreshPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BatchProcessing::removeAllPaths()
|
void BatchProcessing::removeAllPaths()
|
||||||
{
|
{
|
||||||
_ui->pathsList->clear();
|
_ui->pathsList->clear();
|
||||||
|
refreshPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BatchProcessing::browse()
|
void BatchProcessing::browse()
|
||||||
@@ -179,19 +246,7 @@ void BatchProcessing::browse()
|
|||||||
|
|
||||||
void BatchProcessing::openScriptDir()
|
void BatchProcessing::openScriptDir()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_LINUX
|
openDir(_scriptBasePath);
|
||||||
QDBusConnection con = QDBusConnection::sessionBus();
|
|
||||||
QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.FileManager1", "/org/freedesktop/FileManager1", "org.freedesktop.FileManager1", "ShowFolders");
|
|
||||||
QList<QVariant> args = {QStringList(QUrl::fromLocalFile(_scriptBasePath).toString()), QString()};
|
|
||||||
message.setArguments(args);
|
|
||||||
con.call(message);
|
|
||||||
#endif
|
|
||||||
#ifdef Q_OS_WINDOWS
|
|
||||||
QProcess::startDetached("explorer.exe", {QDir::toNativeSeparators(_scriptBasePath)});
|
|
||||||
#endif
|
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(_scriptBasePath));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BatchProcessing::runScript()
|
void BatchProcessing::runScript()
|
||||||
@@ -200,12 +255,9 @@ void BatchProcessing::runScript()
|
|||||||
auto selectedItems = _ui->scriptsList->selectedItems();
|
auto selectedItems = _ui->scriptsList->selectedItems();
|
||||||
if(selectedItems.size())
|
if(selectedItems.size())
|
||||||
{
|
{
|
||||||
_engineThread = new Script::ScriptEngineThread(this);
|
_engineThread = new Script::ScriptEngineThread(_database, this);
|
||||||
connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessage);
|
connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessage);
|
||||||
connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::scriptFinished);
|
connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::scriptFinished);
|
||||||
QStringList paths;
|
|
||||||
for(int i=0; i<_ui->pathsList->count(); i++)
|
|
||||||
paths.append(_ui->pathsList->item(i)->text());
|
|
||||||
|
|
||||||
QFileInfo outDir(_ui->outputPath->text());
|
QFileInfo outDir(_ui->outputPath->text());
|
||||||
if(outDir.exists() && outDir.isWritable())
|
if(outDir.exists() && outDir.isWritable())
|
||||||
@@ -216,7 +268,7 @@ void BatchProcessing::runScript()
|
|||||||
else
|
else
|
||||||
script = ":/scripts/" + script;
|
script = ":/scripts/" + script;
|
||||||
|
|
||||||
_engineThread->setParams(script, scanDirectories(paths), _ui->outputPath->text());
|
_engineThread->setParams(script, _paths, _ui->outputPath->text(), QString());
|
||||||
_engineThread->start();
|
_engineThread->start();
|
||||||
_ui->startButton->setEnabled(false);
|
_ui->startButton->setEnabled(false);
|
||||||
_ui->stopButton->setEnabled(true);
|
_ui->stopButton->setEnabled(true);
|
||||||
@@ -224,6 +276,35 @@ void BatchProcessing::runScript()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
QMessageBox::warning(this, tr("Invalid output directory"), tr("Output directory path doesn't exist or is not writable"));
|
QMessageBox::warning(this, tr("Invalid output directory"), tr("Output directory path doesn't exist or is not writable"));
|
||||||
|
delete _engineThread;
|
||||||
|
_engineThread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatchProcessing::runScript(const QString &script, const QString &arg, bool exit)
|
||||||
|
{
|
||||||
|
_ui->log->clear();
|
||||||
|
{
|
||||||
|
_engineThread = new Script::ScriptEngineThread(_database, this);
|
||||||
|
connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessage);
|
||||||
|
connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessageCli);
|
||||||
|
connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::scriptFinished);
|
||||||
|
if(exit)connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::accept);
|
||||||
|
|
||||||
|
QFileInfo outDir(_ui->outputPath->text());
|
||||||
|
if(outDir.exists() && outDir.isWritable())
|
||||||
|
{
|
||||||
|
_engineThread->setParams(script, _paths, _ui->outputPath->text(), arg);
|
||||||
|
_engineThread->start();
|
||||||
|
_ui->startButton->setEnabled(false);
|
||||||
|
_ui->stopButton->setEnabled(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, tr("Invalid output directory"), tr("Output directory path doesn't exist or is not writable"));
|
||||||
|
delete _engineThread;
|
||||||
|
_engineThread = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,6 +332,14 @@ void BatchProcessing::newMessage(const QString &message, bool error)
|
|||||||
_ui->log->append(message);
|
_ui->log->append(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BatchProcessing::newMessageCli(const QString &message, bool error)
|
||||||
|
{
|
||||||
|
if(error)
|
||||||
|
qWarning() << message;
|
||||||
|
else
|
||||||
|
qDebug() << message;
|
||||||
|
}
|
||||||
|
|
||||||
QJSValue BatchProcessing::getString(const QString &label, const QString &text)
|
QJSValue BatchProcessing::getString(const QString &label, const QString &text)
|
||||||
{
|
{
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
@@ -278,3 +367,107 @@ QJSValue BatchProcessing::getItem(const QStringList &items, const QString &label
|
|||||||
QString ret = QInputDialog::getItem(this, tr("Select item"), label, items, current, false, &ok);
|
QString ret = QInputDialog::getItem(this, tr("Select item"), label, items, current, false, &ok);
|
||||||
return ok ? ret : QJSValue();
|
return ok ? ret : QJSValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJSValue BatchProcessing::question(const QString &question, const QStringList &buttons, const QString &title)
|
||||||
|
{
|
||||||
|
QMessageBox::StandardButtons standardButtons = QMessageBox::NoButton;
|
||||||
|
if(buttons.contains("ok"))standardButtons |= QMessageBox::Ok;
|
||||||
|
if(buttons.contains("yes"))standardButtons |= QMessageBox::Yes;
|
||||||
|
if(buttons.contains("no"))standardButtons |= QMessageBox::No;
|
||||||
|
if(buttons.contains("yesall"))standardButtons |= QMessageBox::YesToAll;
|
||||||
|
if(buttons.contains("noall"))standardButtons |= QMessageBox::NoToAll;
|
||||||
|
if(buttons.contains("abort"))standardButtons |= QMessageBox::Abort;
|
||||||
|
if(buttons.contains("retry"))standardButtons |= QMessageBox::Retry;
|
||||||
|
if(buttons.contains("ignore"))standardButtons |= QMessageBox::Ignore;
|
||||||
|
if(buttons.contains("cancel"))standardButtons |= QMessageBox::Cancel;
|
||||||
|
if(buttons.contains("discard"))standardButtons |= QMessageBox::Discard;
|
||||||
|
if(buttons.contains("apply"))standardButtons |= QMessageBox::Apply;
|
||||||
|
if(buttons.contains("reset"))standardButtons |= QMessageBox::Reset;
|
||||||
|
if(standardButtons == QMessageBox::NoButton)standardButtons = QMessageBox::Ok;
|
||||||
|
|
||||||
|
QMessageBox::StandardButton button = QMessageBox::question(this, title, question, standardButtons);
|
||||||
|
QJSValue ret;
|
||||||
|
switch(button)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case QMessageBox::Ok: ret = "ok"; break;
|
||||||
|
case QMessageBox::Yes: ret = "yes"; break;
|
||||||
|
case QMessageBox::No: ret = "no"; break;
|
||||||
|
case QMessageBox::YesToAll: ret = "yesall"; break;
|
||||||
|
case QMessageBox::NoToAll: ret = "noall"; break;
|
||||||
|
case QMessageBox::Abort: ret = "abort"; break;
|
||||||
|
case QMessageBox::Retry: ret = "retry"; break;
|
||||||
|
case QMessageBox::Ignore: ret = "ignore"; break;
|
||||||
|
case QMessageBox::Cancel: ret = "cancel"; break;
|
||||||
|
case QMessageBox::Discard: ret = "discard"; break;
|
||||||
|
case QMessageBox::Apply: ret = "apply"; break;
|
||||||
|
case QMessageBox::Reset: ret = "reset"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatchProcessing::plot(const QVariant &graph)
|
||||||
|
{
|
||||||
|
ChartGraph *chart = new ChartGraph(this);
|
||||||
|
chart->plot(graph);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleLine::ConsoleLine(QWidget *parent) : QLineEdit(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsoleLine::addLine()
|
||||||
|
{
|
||||||
|
QString line = text();
|
||||||
|
clear();
|
||||||
|
if(_history.size() && _history.last() == line)return;
|
||||||
|
|
||||||
|
_history.append(line);
|
||||||
|
if(_history.size() > 100)_history.removeFirst();
|
||||||
|
_currentLine = _history.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConsoleLine::keyReleaseEvent(QKeyEvent *event)
|
||||||
|
{
|
||||||
|
if(event->key() == Qt::Key_Up)
|
||||||
|
{
|
||||||
|
_currentLine--;
|
||||||
|
if(_currentLine < 0)
|
||||||
|
{
|
||||||
|
_currentLine = -1;
|
||||||
|
clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setText(_history.at(_currentLine));
|
||||||
|
}
|
||||||
|
else if(event->key() == Qt::Key_Down)
|
||||||
|
{
|
||||||
|
_currentLine++;
|
||||||
|
if(_currentLine >= _history.size())
|
||||||
|
{
|
||||||
|
_currentLine = _history.size();
|
||||||
|
clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setText(_history.at(_currentLine));
|
||||||
|
}
|
||||||
|
else QLineEdit::keyReleaseEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void openDir(const QString &path)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
QDBusConnection con = QDBusConnection::sessionBus();
|
||||||
|
QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.FileManager1", "/org/freedesktop/FileManager1", "org.freedesktop.FileManager1", "ShowFolders");
|
||||||
|
QList<QVariant> args = {QStringList(QUrl::fromLocalFile(path).toString()), QString()};
|
||||||
|
message.setArguments(args);
|
||||||
|
con.call(message);
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
QProcess::startDetached("explorer.exe", {QDir::toNativeSeparators(path)});
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -3,10 +3,15 @@
|
|||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
|
#include <QStringListModel>
|
||||||
|
#include <QCompleter>
|
||||||
|
#include <QLineEdit>
|
||||||
#include "scriptengine.h"
|
#include "scriptengine.h"
|
||||||
|
|
||||||
namespace Ui { class BatchProcessing; }
|
namespace Ui { class BatchProcessing; }
|
||||||
|
|
||||||
|
class Database;
|
||||||
|
|
||||||
class BatchProcessing : public QDialog
|
class BatchProcessing : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -14,30 +19,58 @@ class BatchProcessing : public QDialog
|
|||||||
QString _scriptBasePath;
|
QString _scriptBasePath;
|
||||||
QFileSystemWatcher _fileWatcher;
|
QFileSystemWatcher _fileWatcher;
|
||||||
Script::ScriptEngineThread *_engineThread = nullptr;
|
Script::ScriptEngineThread *_engineThread = nullptr;
|
||||||
|
Script::ScriptEngine *_engine = nullptr;
|
||||||
QColor _textColor;
|
QColor _textColor;
|
||||||
|
Database *_database;
|
||||||
|
QStringListModel *_completerModel = nullptr;
|
||||||
|
QCompleter *_completer = nullptr;
|
||||||
|
QList<QPair<QString, QString>> _paths;
|
||||||
private slots:
|
private slots:
|
||||||
void scanScriptDir();
|
void scanScriptDir();
|
||||||
public:
|
public:
|
||||||
explicit BatchProcessing(QWidget *parent = nullptr);
|
explicit BatchProcessing(Database *database, QWidget *parent = nullptr);
|
||||||
~BatchProcessing();
|
~BatchProcessing();
|
||||||
|
void setOutputDir(const QString &output);
|
||||||
|
void setPaths(const QStringList &paths);
|
||||||
protected:
|
protected:
|
||||||
void closeEvent(QCloseEvent *event);
|
void closeEvent(QCloseEvent *event);
|
||||||
|
void refreshPaths();
|
||||||
public slots:
|
public slots:
|
||||||
void addFiles();
|
void addFiles();
|
||||||
void addDir();
|
void addDir();
|
||||||
|
void addMarked();
|
||||||
void removePath();
|
void removePath();
|
||||||
void removeAllPaths();
|
void removeAllPaths();
|
||||||
void browse();
|
void browse();
|
||||||
void openScriptDir();
|
void openScriptDir();
|
||||||
void runScript();
|
void runScript();
|
||||||
|
void runScript(const QString &script, const QString &arg, bool exit);
|
||||||
void stopScript();
|
void stopScript();
|
||||||
void scriptFinished();
|
void scriptFinished();
|
||||||
void newMessage(const QString &message, bool error);
|
void newMessage(const QString &message, bool error);
|
||||||
|
void newMessageCli(const QString &message, bool error);
|
||||||
|
|
||||||
QJSValue getString(const QString &label, const QString &text);
|
QJSValue getString(const QString &label, const QString &text);
|
||||||
QJSValue getInt(const QString &label, int value);
|
QJSValue getInt(const QString &label, int value);
|
||||||
QJSValue getFloat(const QString &label, double value, int decimals);
|
QJSValue getFloat(const QString &label, double value, int decimals);
|
||||||
QJSValue getItem(const QStringList &items, const QString &label, int current);
|
QJSValue getItem(const QStringList &items, const QString &label, int current);
|
||||||
|
QJSValue question(const QString &question, const QStringList &buttons, const QString &title = "");
|
||||||
|
|
||||||
|
void plot(const QVariant &graph);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ConsoleLine : public QLineEdit
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ConsoleLine(QWidget *parent = nullptr);
|
||||||
|
void addLine();
|
||||||
|
void keyReleaseEvent(QKeyEvent *event) override;
|
||||||
|
private:
|
||||||
|
int _currentLine = 0;
|
||||||
|
QStringList _history;
|
||||||
|
};
|
||||||
|
|
||||||
|
void openDir(const QString &path);
|
||||||
|
|
||||||
#endif // BATCHPROCESSING_H
|
#endif // BATCHPROCESSING_H
|
||||||
@@ -43,6 +43,9 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Add files</string>
|
<string>Add files</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@@ -50,6 +53,19 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Add directories</string>
|
<string>Add directories</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="addMarkedButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add marked</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@@ -57,6 +73,9 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Remove</string>
|
<string>Remove</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@@ -64,6 +83,9 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Remove all</string>
|
<string>Remove all</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -91,6 +113,9 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Browse</string>
|
<string>Browse</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -122,6 +147,9 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Open scripts</string>
|
<string>Open scripts</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -164,23 +192,30 @@
|
|||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer_2">
|
<widget class="ConsoleLine" name="consoleLineEdit">
|
||||||
<property name="orientation">
|
<property name="placeholderText">
|
||||||
<enum>Qt::Horizontal</enum>
|
<string>Console</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
</widget>
|
||||||
<size>
|
</item>
|
||||||
<width>40</width>
|
<item>
|
||||||
<height>20</height>
|
<widget class="QPushButton" name="executeButton">
|
||||||
</size>
|
<property name="text">
|
||||||
|
<string>Execute</string>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="startButton">
|
<widget class="QPushButton" name="startButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Start script</string>
|
<string>Start script</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@@ -191,12 +226,8 @@
|
|||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Stop script</string>
|
<string>Stop script</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<property name="autoDefault">
|
||||||
</item>
|
<bool>false</bool>
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="closeButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Close</string>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -204,23 +235,29 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>ConsoleLine</class>
|
||||||
|
<extends>QLineEdit</extends>
|
||||||
|
<header>batchprocessing.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>pathsList</tabstop>
|
||||||
|
<tabstop>addFilesButton</tabstop>
|
||||||
|
<tabstop>addDirButton</tabstop>
|
||||||
|
<tabstop>addMarkedButton</tabstop>
|
||||||
|
<tabstop>removeButton</tabstop>
|
||||||
|
<tabstop>removeAllButton</tabstop>
|
||||||
|
<tabstop>browseButton</tabstop>
|
||||||
|
<tabstop>openScriptsButton</tabstop>
|
||||||
|
<tabstop>scriptsList</tabstop>
|
||||||
|
<tabstop>consoleLineEdit</tabstop>
|
||||||
|
<tabstop>startButton</tabstop>
|
||||||
|
<tabstop>stopButton</tabstop>
|
||||||
|
<tabstop>outputPath</tabstop>
|
||||||
|
<tabstop>log</tabstop>
|
||||||
|
</tabstops>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections>
|
<connections/>
|
||||||
<connection>
|
|
||||||
<sender>closeButton</sender>
|
|
||||||
<signal>released()</signal>
|
|
||||||
<receiver>BatchProcessing</receiver>
|
|
||||||
<slot>close()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>973</x>
|
|
||||||
<y>745</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>511</x>
|
|
||||||
<y>383</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
</ui>
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
#include "chartgraph.h"
|
||||||
|
#include <QChartView>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QLineSeries>
|
||||||
|
#include <QBarSeries>
|
||||||
|
#include <QBarSet>
|
||||||
|
#include <QBarCategoryAxis>
|
||||||
|
#include <QScatterSeries>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMenuBar>
|
||||||
|
#include <QValueAxis>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QToolBar>
|
||||||
|
#include <QStyle>
|
||||||
|
|
||||||
|
class ChartView : public QChartView
|
||||||
|
{
|
||||||
|
QPointF _mousePos;
|
||||||
|
bool _scroll = false;
|
||||||
|
public:
|
||||||
|
ChartView(QWidget *parent) : QChartView(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
void keyPressEvent(QKeyEvent *event) override
|
||||||
|
{
|
||||||
|
if(!chart()->isZoomed())chart()->zoom(0.999999);//workaround so zoomReset() reset scroll
|
||||||
|
switch(event->key())
|
||||||
|
{
|
||||||
|
case Qt::Key_Plus:
|
||||||
|
chart()->zoomIn();
|
||||||
|
break;
|
||||||
|
case Qt::Key_Minus:
|
||||||
|
chart()->zoomOut();
|
||||||
|
break;
|
||||||
|
case Qt::Key_Left:
|
||||||
|
chart()->scroll(-10, 0);
|
||||||
|
break;
|
||||||
|
case Qt::Key_Right:
|
||||||
|
chart()->scroll(10, 0);
|
||||||
|
break;
|
||||||
|
case Qt::Key_Up:
|
||||||
|
chart()->scroll(0, 10);
|
||||||
|
break;
|
||||||
|
case Qt::Key_Down:
|
||||||
|
chart()->scroll(0, -10);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
QGraphicsView::keyPressEvent(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void mousePressEvent(QMouseEvent *event) override
|
||||||
|
{
|
||||||
|
if(event->button() == Qt::LeftButton)
|
||||||
|
{
|
||||||
|
_scroll = true;
|
||||||
|
if(!chart()->isZoomed())chart()->zoom(0.999999);//workaround so zoomReset() reset scroll
|
||||||
|
_mousePos = event->position();
|
||||||
|
}
|
||||||
|
|
||||||
|
QChartView::mousePressEvent(event);
|
||||||
|
}
|
||||||
|
void mouseMoveEvent(QMouseEvent *event) override
|
||||||
|
{
|
||||||
|
if(_scroll)
|
||||||
|
{
|
||||||
|
QPointF pos = event->position();
|
||||||
|
chart()->scroll(_mousePos.x() - pos.x(), pos.y() - _mousePos.y());
|
||||||
|
_mousePos = pos;
|
||||||
|
}
|
||||||
|
QChartView::mouseMoveEvent(event);
|
||||||
|
}
|
||||||
|
void mouseReleaseEvent(QMouseEvent *event) override
|
||||||
|
{
|
||||||
|
_scroll = false;
|
||||||
|
QChartView::mouseReleaseEvent(event);
|
||||||
|
}
|
||||||
|
void wheelEvent(QWheelEvent *event) override
|
||||||
|
{
|
||||||
|
if(event->angleDelta().y() > 0)
|
||||||
|
chart()->zoomIn();
|
||||||
|
if(event->angleDelta().y() < 0)
|
||||||
|
chart()->zoomOut();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ChartGraph::ChartGraph(QWidget *parent) : QMainWindow(parent)
|
||||||
|
{
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
setWindowTitle(tr("Chart"));
|
||||||
|
|
||||||
|
_chartView = new ChartView(this);
|
||||||
|
setCentralWidget(_chartView);
|
||||||
|
|
||||||
|
_chart = new QChart;
|
||||||
|
_chartView->setChart(_chart);
|
||||||
|
_chartView->setRenderHint(QPainter::Antialiasing);
|
||||||
|
resize(1024, 768);
|
||||||
|
|
||||||
|
menuBar()->addAction(tr("Save"), this, &ChartGraph::save);
|
||||||
|
menuBar()->addAction(tr("Reset view"), [this](){ _chart->zoomReset(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChartGraph::plot(const QVariant &graph)
|
||||||
|
{
|
||||||
|
QVariantMap map = graph.toMap();
|
||||||
|
|
||||||
|
_chart->setTitle(map["title"].toString());
|
||||||
|
if(map.contains("legend"))
|
||||||
|
{
|
||||||
|
QVariantMap legend = map["legend"].toMap();
|
||||||
|
if(legend.contains("visible"))
|
||||||
|
_chart->legend()->setVisible(legend["visible"].toBool());
|
||||||
|
|
||||||
|
QString align = legend["align"].toString();
|
||||||
|
if(align == "top")
|
||||||
|
_chart->legend()->setAlignment(Qt::AlignTop);
|
||||||
|
else if(align == "left")
|
||||||
|
_chart->legend()->setAlignment(Qt::AlignLeft);
|
||||||
|
else if(align == "bottom")
|
||||||
|
_chart->legend()->setAlignment(Qt::AlignBottom);
|
||||||
|
else if(align == "right")
|
||||||
|
_chart->legend()->setAlignment(Qt::AlignRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
QBarSeries *barSeries = nullptr;
|
||||||
|
|
||||||
|
qreal minX = INFINITY;
|
||||||
|
qreal maxX = -INFINITY;
|
||||||
|
qreal minY = INFINITY;
|
||||||
|
qreal maxY = -INFINITY;
|
||||||
|
qreal minY2 = INFINITY;
|
||||||
|
qreal maxY2 = -INFINITY;
|
||||||
|
|
||||||
|
QValueAxis *xaxis = new QValueAxis(_chart);
|
||||||
|
QBarCategoryAxis *barxaxis = new QBarCategoryAxis(_chart);
|
||||||
|
QValueAxis *yaxis = new QValueAxis(_chart);
|
||||||
|
QValueAxis *y2axis = new QValueAxis(_chart);
|
||||||
|
_chart->addAxis(xaxis, Qt::AlignBottom);
|
||||||
|
_chart->addAxis(yaxis, Qt::AlignLeft);
|
||||||
|
_chart->addAxis(y2axis, Qt::AlignRight);
|
||||||
|
_chart->addAxis(barxaxis, Qt::AlignBottom);
|
||||||
|
y2axis->setGridLinePen(Qt::DashDotLine);
|
||||||
|
|
||||||
|
for(auto s : map["series"].toList())
|
||||||
|
{
|
||||||
|
QVariantMap serie = s.toMap();
|
||||||
|
QString type = serie["type"].toString();
|
||||||
|
bool y2 = serie["y2"].toBool();
|
||||||
|
|
||||||
|
if(type == "line" || type == "points" || type == "linePoints" || type.isEmpty())
|
||||||
|
{
|
||||||
|
QXYSeries *series = nullptr;
|
||||||
|
if(type == "points")
|
||||||
|
{
|
||||||
|
QScatterSeries *scatter = new QScatterSeries(_chart);
|
||||||
|
series = scatter;
|
||||||
|
QString shape = serie["shape"].toString();
|
||||||
|
if(shape == "circle")scatter->setMarkerShape(QScatterSeries::MarkerShapeCircle);
|
||||||
|
else if(shape == "rectangle")scatter->setMarkerShape(QScatterSeries::MarkerShapeRectangle);
|
||||||
|
else if(shape == "triangle")scatter->setMarkerShape(QScatterSeries::MarkerShapeTriangle);
|
||||||
|
else if(shape == "star")scatter->setMarkerShape(QScatterSeries::MarkerShapeStar);
|
||||||
|
else if(shape == "pentagon")scatter->setMarkerShape(QScatterSeries::MarkerShapePentagon);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
series = new QLineSeries(_chart);
|
||||||
|
}
|
||||||
|
|
||||||
|
series->setName(serie["title"].toString());
|
||||||
|
QVariantList x = serie["x"].toList();
|
||||||
|
QVariantList y = serie["y"].toList();
|
||||||
|
if(x.isEmpty())
|
||||||
|
{
|
||||||
|
for(int i = 0; i < y.size(); i++)
|
||||||
|
{
|
||||||
|
qreal val = y[i].toDouble();
|
||||||
|
if(y2)
|
||||||
|
{
|
||||||
|
minY2 = std::min(minY2, val);
|
||||||
|
maxY2 = std::max(maxY2, val);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
minY = std::min(minY, val);
|
||||||
|
maxY = std::max(maxY, val);
|
||||||
|
}
|
||||||
|
series->append(i, val);
|
||||||
|
}
|
||||||
|
minX = std::min(minX, 0.0);
|
||||||
|
maxX = std::max(maxX, y.size() - 1.0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int size = std::min(x.size(), y.size());
|
||||||
|
for(int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
qreal val = y[i].toDouble();
|
||||||
|
if(y2)
|
||||||
|
{
|
||||||
|
minY2 = std::min(minY2, val);
|
||||||
|
maxY2 = std::max(maxY2, val);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
minY = std::min(minY, val);
|
||||||
|
maxY = std::max(maxY, val);
|
||||||
|
}
|
||||||
|
minX = std::min(minX, x[i].toDouble());
|
||||||
|
maxX = std::max(maxX, x[i].toDouble());
|
||||||
|
series->append(x[i].toDouble(), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_chart->addSeries(series);
|
||||||
|
series->attachAxis(xaxis);
|
||||||
|
series->attachAxis(y2 ? y2axis : yaxis);
|
||||||
|
|
||||||
|
if(serie.contains("color"))
|
||||||
|
{
|
||||||
|
QString color = serie["color"].toString();
|
||||||
|
if(QColor::isValidColorName(color))series->setColor(QColor::fromString(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(serie["bestFit"].toBool())
|
||||||
|
{
|
||||||
|
series->setBestFitLineVisible(true);
|
||||||
|
QPen pen = series->bestFitLinePen();
|
||||||
|
pen.setColor(series->color());
|
||||||
|
pen.setStyle(Qt::DashLine);
|
||||||
|
series->setBestFitLinePen(pen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type == "linePoints")
|
||||||
|
series->setPointsVisible(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if(type == "bar")
|
||||||
|
{
|
||||||
|
if(!barSeries)
|
||||||
|
{
|
||||||
|
barSeries = new QBarSeries(_chart);
|
||||||
|
_chart->addSeries(barSeries);
|
||||||
|
barSeries->attachAxis(yaxis);
|
||||||
|
barSeries->attachAxis(barxaxis);
|
||||||
|
}
|
||||||
|
QBarSet *set = new QBarSet(serie["title"].toString());
|
||||||
|
QVariantList y = serie["y"].toList();
|
||||||
|
for(int i = 0; i < y.size(); i++)
|
||||||
|
{
|
||||||
|
qreal val = y[i].toDouble();
|
||||||
|
minY = std::min(minY, val);
|
||||||
|
maxY = std::max(maxY, val);
|
||||||
|
set->append(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
barSeries->append(set);
|
||||||
|
for(int i = barxaxis->count() + 1; i <= y.size(); i++)
|
||||||
|
barxaxis->append(QString::number(i));
|
||||||
|
|
||||||
|
if(serie.contains("color"))
|
||||||
|
{
|
||||||
|
QString color = serie["color"].toString();
|
||||||
|
if(QColor::isValidColorName(color))set->setColor(QColor::fromString(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(barSeries)
|
||||||
|
{
|
||||||
|
xaxis->setRange(std::min(minX, -0.5), std::max(maxX, barxaxis->count() - 0.5));
|
||||||
|
minY = std::min(minY, 0.0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
xaxis->setRange(minX, maxX);
|
||||||
|
}
|
||||||
|
|
||||||
|
yaxis->setRange(minY, maxY);
|
||||||
|
y2axis->setRange(minY2, maxY2);
|
||||||
|
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChartGraph::save()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
QString dir = settings.value("mainwindow/lastdir").toString();
|
||||||
|
QString output = QFileDialog::getSaveFileName(this, tr("Save as"), dir, "PNG (*.png)");
|
||||||
|
|
||||||
|
if(!output.isEmpty())
|
||||||
|
{
|
||||||
|
QPixmap graph = _chartView->grab();
|
||||||
|
graph.toImage().save(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef CHARTGRAPH_H
|
||||||
|
#define CHARTGRAPH_H
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QJSValue>
|
||||||
|
#include <QChart>
|
||||||
|
|
||||||
|
class ChartView;
|
||||||
|
|
||||||
|
class ChartGraph : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QChart *_chart;
|
||||||
|
ChartView *_chartView;
|
||||||
|
public:
|
||||||
|
explicit ChartGraph(QWidget *parent = nullptr);
|
||||||
|
void plot(const QVariant &graph);
|
||||||
|
public slots:
|
||||||
|
void save();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CHARTGRAPH_H
|
||||||
+107
-19
@@ -4,7 +4,7 @@
|
|||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include "loadrunable.h"
|
#include "loadimage.h"
|
||||||
|
|
||||||
Database::Database(QObject *parent) : QObject(parent)
|
Database::Database(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
@@ -15,11 +15,30 @@ bool Database::init(const QLatin1String &connectionName)
|
|||||||
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
QDir dir(path);
|
QDir dir(path);
|
||||||
|
|
||||||
QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
|
database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
|
||||||
|
ngc = QSqlDatabase::addDatabase("QSQLITE", connectionName + "ngc");
|
||||||
|
|
||||||
if(!dir.mkpath("."))
|
if(!dir.mkpath("."))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if(ngc.isValid())
|
||||||
|
{
|
||||||
|
QString ngcDb = dir.absoluteFilePath("ngc.db");
|
||||||
|
if(!QFile::exists(ngcDb))
|
||||||
|
QFile::copy(":/ngc.db", ngcDb);
|
||||||
|
|
||||||
|
ngc.setDatabaseName(ngcDb);
|
||||||
|
if(ngc.open())
|
||||||
|
{
|
||||||
|
m_getNgc = QSqlQuery(ngc);
|
||||||
|
m_getNgc.prepare("SELECT *,IIF(V_Mag IS NULL, B_Mag, V_Mag) AS mag FROM ngc WHERE RA_deg BETWEEN ? AND ? AND DEC_deg BETWEEN ? AND ?");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "Could not open NGC database";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(database.isValid())
|
if(database.isValid())
|
||||||
{
|
{
|
||||||
database.setDatabaseName(dir.absoluteFilePath("database2.db"));
|
database.setDatabaseName(dir.absoluteFilePath("database2.db"));
|
||||||
@@ -27,7 +46,7 @@ bool Database::init(const QLatin1String &connectionName)
|
|||||||
{
|
{
|
||||||
QSqlQuery query(database);
|
QSqlQuery query(database);
|
||||||
query.exec("PRAGMA foreign_keys = ON");
|
query.exec("PRAGMA foreign_keys = ON");
|
||||||
int version = checkVersion();
|
int version = checkVersion(database);
|
||||||
if(version == 0)
|
if(version == 0)
|
||||||
{
|
{
|
||||||
query.exec("PRAGMA user_version = 1");
|
query.exec("PRAGMA user_version = 1");
|
||||||
@@ -42,10 +61,17 @@ bool Database::init(const QLatin1String &connectionName)
|
|||||||
query.exec("CREATE INDEX IF NOT EXISTS maxRa_idx ON fits_files(maxRa)");
|
query.exec("CREATE INDEX IF NOT EXISTS maxRa_idx ON fits_files(maxRa)");
|
||||||
query.exec("CREATE INDEX IF NOT EXISTS minDec_idx ON fits_files(minDec)");
|
query.exec("CREATE INDEX IF NOT EXISTS minDec_idx ON fits_files(minDec)");
|
||||||
query.exec("CREATE INDEX IF NOT EXISTS maxDec_idx ON fits_files(maxDec)");
|
query.exec("CREATE INDEX IF NOT EXISTS maxDec_idx ON fits_files(maxDec)");
|
||||||
|
version = 1;
|
||||||
}
|
}
|
||||||
else if(version > 1)
|
if(version == 1)
|
||||||
{
|
{
|
||||||
qDebug() << "Database version is too new";
|
query.exec("CREATE INDEX IF NOT EXISTS id_file_key ON fits_headers(id_file, key)");
|
||||||
|
query.exec("PRAGMA user_version = 2");
|
||||||
|
version = 2;
|
||||||
|
}
|
||||||
|
if(version > 2)
|
||||||
|
{
|
||||||
|
qWarning() << "Database version is too new";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,8 +100,16 @@ bool Database::init(const QLatin1String &connectionName)
|
|||||||
m_deleteFile.prepare("DELETE FROM fits_files WHERE id=?");
|
m_deleteFile.prepare("DELETE FROM fits_files WHERE id=?");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
qDebug() << error.text();
|
qWarning() << error.text();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "Failed to open database" << connectionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "Database is invalid";
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -141,21 +175,20 @@ bool Database::checkError(QSqlQuery &query)
|
|||||||
return true;
|
return true;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
qDebug() << error.text();
|
qWarning() << error.text();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Database::checkVersion()
|
int Database::checkVersion(QSqlDatabase &db)
|
||||||
{
|
{
|
||||||
QSqlDatabase db = QSqlDatabase::database();
|
QSqlQuery query("PRAGMA user_version", db);
|
||||||
QSqlQuery query("PRAGMA user_version");
|
|
||||||
if(query.next())
|
if(query.next())
|
||||||
return query.value(0).toInt();
|
return query.value(0).toInt();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QStringList nameFilters = {"*.fit", "*.fits", "*.fz", "*.xisf"};
|
static QStringList nameFilters = {"*.fit", "*.fits", "*.fz", "*.fts", "*.xisf"};
|
||||||
|
|
||||||
static int countFiles(const QDir &dir, QStringList &scannedDirs)
|
static int countFiles(const QDir &dir, QStringList &scannedDirs)
|
||||||
{
|
{
|
||||||
@@ -175,7 +208,6 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
|
|||||||
QStringList scannedDirs;
|
QStringList scannedDirs;
|
||||||
int count = countFiles(dir, scannedDirs);
|
int count = countFiles(dir, scannedDirs);
|
||||||
progress->setMaximum(count);
|
progress->setMaximum(count);
|
||||||
QSqlDatabase database = QSqlDatabase::database();
|
|
||||||
database.transaction();
|
database.transaction();
|
||||||
|
|
||||||
scannedDirs.clear();
|
scannedDirs.clear();
|
||||||
@@ -193,7 +225,6 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
|
|||||||
void Database::reindex(QProgressDialog *progress)
|
void Database::reindex(QProgressDialog *progress)
|
||||||
{
|
{
|
||||||
QVariantList deleteids;
|
QVariantList deleteids;
|
||||||
QSqlDatabase database = QSqlDatabase::database();
|
|
||||||
database.transaction();
|
database.transaction();
|
||||||
QSqlQuery size("SELECT COUNT(*) FROM fits_files", database);
|
QSqlQuery size("SELECT COUNT(*) FROM fits_files", database);
|
||||||
size.next();
|
size.next();
|
||||||
@@ -232,6 +263,62 @@ QStringList Database::getFitsKeywords()
|
|||||||
return keywords;
|
return keywords;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<SkyObject> Database::getObjects(double minRa, double maxRa, double minDec, double maxDec)
|
||||||
|
{
|
||||||
|
QVector<SkyObject> objects;
|
||||||
|
if(!ngc.isOpen())return objects;
|
||||||
|
|
||||||
|
m_getNgc.bindValue(0, minRa);
|
||||||
|
m_getNgc.bindValue(1, maxRa);
|
||||||
|
m_getNgc.bindValue(2, minDec);
|
||||||
|
m_getNgc.bindValue(3, maxDec);
|
||||||
|
|
||||||
|
if(m_getNgc.exec())
|
||||||
|
{
|
||||||
|
while(m_getNgc.next())
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QString name2;
|
||||||
|
QString m = m_getNgc.value("M").toString();
|
||||||
|
QString ic = m_getNgc.value("IC").toString();
|
||||||
|
if(!m.isEmpty())
|
||||||
|
{
|
||||||
|
name = "M" + m;
|
||||||
|
m.clear();
|
||||||
|
}
|
||||||
|
else if(!ic.isEmpty())
|
||||||
|
{
|
||||||
|
name = "IC" + ic;
|
||||||
|
ic.clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
name = m_getNgc.value("Name").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!ic.isEmpty())name2 += "IC" + ic + " ";
|
||||||
|
name2 += m_getNgc.value("Common names").toString();
|
||||||
|
|
||||||
|
objects.append({
|
||||||
|
name,
|
||||||
|
name2,
|
||||||
|
{m_getNgc.value("RA_deg").toDouble(), m_getNgc.value("DEC_deg").toDouble()},
|
||||||
|
m_getNgc.value("MajAx").toDouble(),
|
||||||
|
m_getNgc.value("MinAx").toDouble(),
|
||||||
|
m_getNgc.value("PosAng").toDouble(),
|
||||||
|
m_getNgc.value("mag").isNull() ? NAN : m_getNgc.value("mag").toDouble(),
|
||||||
|
{0, 0},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSqlDatabase &Database::db() const
|
||||||
|
{
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
|
||||||
bool Database::indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs)
|
bool Database::indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs)
|
||||||
{
|
{
|
||||||
if(scannedDirs.contains(dir.canonicalPath()))return true;
|
if(scannedDirs.contains(dir.canonicalPath()))return true;
|
||||||
@@ -272,10 +359,10 @@ bool Database::indexFile(const QFileInfo &file)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok;
|
bool ok = false;
|
||||||
if(filePath.endsWith(".xisf", Qt::CaseInsensitive))
|
if(isXISF(file.suffix()))
|
||||||
ok = readXISFHeader(filePath, info);
|
ok = readXISFHeader(filePath, info);
|
||||||
else
|
else if(isFITS(file.suffix()))
|
||||||
ok = readFITSHeader(filePath, info);
|
ok = readFITSHeader(filePath, info);
|
||||||
|
|
||||||
qlonglong last_id = -1;
|
qlonglong last_id = -1;
|
||||||
@@ -296,7 +383,7 @@ bool Database::indexFile(const QFileInfo &file)
|
|||||||
m_insertFileWcs.bindValue(7, crVal2);
|
m_insertFileWcs.bindValue(7, crVal2);
|
||||||
if(!m_insertFileWcs.exec())
|
if(!m_insertFileWcs.exec())
|
||||||
{
|
{
|
||||||
qDebug() << "Database error" << m_insertFileWcs.lastError();
|
qWarning() << "Database error" << m_insertFileWcs.lastError();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
last_id = m_insertFileWcs.lastInsertId().toLongLong();
|
last_id = m_insertFileWcs.lastInsertId().toLongLong();
|
||||||
@@ -307,7 +394,7 @@ bool Database::indexFile(const QFileInfo &file)
|
|||||||
m_insertFile.bindValue(1, mtime);
|
m_insertFile.bindValue(1, mtime);
|
||||||
if(!m_insertFile.exec())
|
if(!m_insertFile.exec())
|
||||||
{
|
{
|
||||||
qDebug() << "Database error" << m_insertFile.lastError();
|
qWarning() << "Database error" << m_insertFile.lastError();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
last_id = m_insertFile.lastInsertId().toLongLong();
|
last_id = m_insertFile.lastInsertId().toLongLong();
|
||||||
@@ -316,6 +403,7 @@ bool Database::indexFile(const QFileInfo &file)
|
|||||||
QVariantList file_id, keys, values, comments;
|
QVariantList file_id, keys, values, comments;
|
||||||
for(const auto &record : info.fitsHeader)
|
for(const auto &record : info.fitsHeader)
|
||||||
{
|
{
|
||||||
|
if(record.xisf && record.key.startsWith("PCL:"))continue;
|
||||||
file_id << last_id;
|
file_id << last_id;
|
||||||
keys << QString(record.key);
|
keys << QString(record.key);
|
||||||
values << record.value.toString();
|
values << record.value.toString();
|
||||||
@@ -327,7 +415,7 @@ bool Database::indexFile(const QFileInfo &file)
|
|||||||
m_insertFitsHeader.bindValue(3, comments);
|
m_insertFitsHeader.bindValue(3, comments);
|
||||||
if(!m_insertFitsHeader.execBatch())
|
if(!m_insertFitsHeader.execBatch())
|
||||||
{
|
{
|
||||||
qDebug() << "Database error" << m_insertFitsHeader.lastError();
|
qWarning() << "Database error" << m_insertFitsHeader.lastError();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,13 @@
|
|||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QProgressDialog>
|
#include <QProgressDialog>
|
||||||
|
#include "imageinfodata.h"
|
||||||
|
|
||||||
class Database : public QObject
|
class Database : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
QSqlDatabase database;
|
||||||
|
QSqlDatabase ngc;
|
||||||
QSqlQuery m_markQuery;
|
QSqlQuery m_markQuery;
|
||||||
QSqlQuery m_unmarkQuery;
|
QSqlQuery m_unmarkQuery;
|
||||||
QSqlQuery m_isMarkedQuery;
|
QSqlQuery m_isMarkedQuery;
|
||||||
@@ -21,6 +24,8 @@ class Database : public QObject
|
|||||||
QSqlQuery m_headerKeywords;
|
QSqlQuery m_headerKeywords;
|
||||||
QSqlQuery m_deleteFile;
|
QSqlQuery m_deleteFile;
|
||||||
|
|
||||||
|
QSqlQuery m_getNgc;
|
||||||
|
|
||||||
int m_progress;
|
int m_progress;
|
||||||
public:
|
public:
|
||||||
explicit Database(QObject *parent = 0);
|
explicit Database(QObject *parent = 0);
|
||||||
@@ -36,11 +41,13 @@ public:
|
|||||||
void indexDir(const QDir &dir, QProgressDialog *progress);
|
void indexDir(const QDir &dir, QProgressDialog *progress);
|
||||||
void reindex(QProgressDialog *progress);
|
void reindex(QProgressDialog *progress);
|
||||||
QStringList getFitsKeywords();
|
QStringList getFitsKeywords();
|
||||||
|
QVector<SkyObject> getObjects(double minRa, double maxRa, double minDec, double maxDec);
|
||||||
|
const QSqlDatabase& db() const;
|
||||||
protected:
|
protected:
|
||||||
bool indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs);
|
bool indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs);
|
||||||
bool indexFile(const QFileInfo &file);
|
bool indexFile(const QFileInfo &file);
|
||||||
bool checkError(QSqlQuery &query);
|
bool checkError(QSqlQuery &query);
|
||||||
int checkVersion();
|
int checkVersion(QSqlDatabase &db);
|
||||||
signals:
|
signals:
|
||||||
void databaseChanged();
|
void databaseChanged();
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,585 @@
|
|||||||
|
#include "databasetree.h"
|
||||||
|
|
||||||
|
#include "database.h"
|
||||||
|
#include "databaseview.h"
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <QStackedWidget>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
DatabaseTreeSettings::DatabaseTreeSettings(const QStringList &data, QStringList keywords, QWidget *parent) : QDialog(parent)
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("Add tree filter"));
|
||||||
|
QVBoxLayout *vlayout = new QVBoxLayout(this);
|
||||||
|
setLayout(vlayout);
|
||||||
|
|
||||||
|
QStringList key = data[0].split('/');
|
||||||
|
|
||||||
|
qsizetype dateobsindex = keywords.indexOf("DATE-OBS");
|
||||||
|
if(dateobsindex != -1)
|
||||||
|
{
|
||||||
|
keywords.insert(dateobsindex + 1, "DATE-OBS_YEAR-MONTH-DAY");
|
||||||
|
keywords.insert(dateobsindex + 1, "DATE-OBS_YEAR-MONTH");
|
||||||
|
keywords.insert(dateobsindex + 1, "DATE-OBS_YEAR");
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
QComboBox *comboxBox = new QComboBox(this);
|
||||||
|
comboxBox->addItem("");
|
||||||
|
comboxBox->addItems(keywords);
|
||||||
|
vlayout->addWidget(comboxBox);
|
||||||
|
_keywordsSelect.append(comboxBox);
|
||||||
|
if(i < key.size() && keywords.contains(key[i]))
|
||||||
|
comboxBox->setCurrentText(key[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
vlayout->addWidget(new QLabel(tr("Aggregate function"), this));
|
||||||
|
_aggregateFunction = new QComboBox(this);
|
||||||
|
_aggregateFunction->addItems({"", "SUM", "COUNT", "AVG", "MIN", "MAX", "MEDIAN"});
|
||||||
|
vlayout->addWidget(_aggregateFunction);
|
||||||
|
_aggregateFunction->setToolTip(tr("This aggregate function will be applied to last level of grouping"));
|
||||||
|
_aggregateFunction->setCurrentText(data[1]);
|
||||||
|
|
||||||
|
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &DatabaseTreeSettings::acceptButton);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &DatabaseTreeSettings::reject);
|
||||||
|
vlayout->addWidget(buttonBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseTreeSettings::keywords() const
|
||||||
|
{
|
||||||
|
QStringList keywords;
|
||||||
|
for(QComboBox *box : _keywordsSelect)
|
||||||
|
{
|
||||||
|
if(box->currentIndex() > 0)
|
||||||
|
keywords.append(box->currentText());
|
||||||
|
}
|
||||||
|
return keywords.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseTreeSettings::aggregrationFunc() const
|
||||||
|
{
|
||||||
|
return _aggregateFunction->currentText();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTreeSettings::acceptButton()
|
||||||
|
{
|
||||||
|
for(QComboBox *box : _keywordsSelect)
|
||||||
|
{
|
||||||
|
if(box->currentIndex() > 0)
|
||||||
|
{
|
||||||
|
QDialog::accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QDialog::reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TreeNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TreeNode() = default;
|
||||||
|
TreeNode(TreeNode *parent, const QVariant value, int level)
|
||||||
|
:_parent(parent)
|
||||||
|
,_value(value)
|
||||||
|
,_level(level)
|
||||||
|
{}
|
||||||
|
const TreeNode* child(size_t idx) const
|
||||||
|
{
|
||||||
|
if(idx >= 0 && idx < _children.size())
|
||||||
|
return _children[idx].get();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
TreeNode* child(size_t idx)
|
||||||
|
{
|
||||||
|
if(idx >= 0 && idx < _children.size())
|
||||||
|
return _children[idx].get();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
TreeNode* parent() const
|
||||||
|
{
|
||||||
|
return _parent;
|
||||||
|
}
|
||||||
|
int row() const
|
||||||
|
{
|
||||||
|
if(_parent)
|
||||||
|
return _parent->indexOf(this);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int childCount() const
|
||||||
|
{
|
||||||
|
if(!_init)return 1;
|
||||||
|
return _children.size();
|
||||||
|
}
|
||||||
|
const QVariant& value() const
|
||||||
|
{
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
void fill(const QVariantList &list)
|
||||||
|
{
|
||||||
|
_init = true;
|
||||||
|
for(auto &item : list)
|
||||||
|
_children.push_back(std::make_unique<TreeNode>(this, item, _level + 1));
|
||||||
|
}
|
||||||
|
bool filled() const
|
||||||
|
{
|
||||||
|
return _init;
|
||||||
|
}
|
||||||
|
int level() const
|
||||||
|
{
|
||||||
|
return _level;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int indexOf(const TreeNode *child) const
|
||||||
|
{
|
||||||
|
auto f = [child](const std::unique_ptr<TreeNode> &i){ return i.get() == child; };
|
||||||
|
auto it = std::find_if(_children.begin(), _children.end(), f);
|
||||||
|
if(it != _children.end())return std::distance(_children.begin(), it);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
TreeNode *_parent = nullptr;
|
||||||
|
QVariant _value;
|
||||||
|
std::vector<std::unique_ptr<TreeNode>> _children;
|
||||||
|
bool _init = false;
|
||||||
|
int _level = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
DatabaseTree::DatabaseTree(Database *database, QObject *parent) : QAbstractItemModel(parent)
|
||||||
|
,_database(database)
|
||||||
|
{
|
||||||
|
_italicFont.setItalic(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTree::setKeys(const QStringList &keys)
|
||||||
|
{
|
||||||
|
_keys = keys;
|
||||||
|
if(!_loaded)return;
|
||||||
|
beginResetModel();
|
||||||
|
prepareQueries();
|
||||||
|
_rootNode = std::make_unique<TreeNode>();
|
||||||
|
fillNode(_rootNode.get());
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList DatabaseTree::keys() const
|
||||||
|
{
|
||||||
|
return _keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex DatabaseTree::index(int row, int column, const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if(!hasIndex(row, column, parent))
|
||||||
|
return QModelIndex();
|
||||||
|
|
||||||
|
TreeNode *node;
|
||||||
|
|
||||||
|
if(!parent.isValid())
|
||||||
|
node = _rootNode.get();
|
||||||
|
else
|
||||||
|
node = static_cast<TreeNode*>(parent.internalPointer());
|
||||||
|
|
||||||
|
if(node)
|
||||||
|
{
|
||||||
|
TreeNode *child = node->child(row);
|
||||||
|
if(child)return createIndex(row, column, child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex DatabaseTree::parent(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if(!index.isValid())
|
||||||
|
return QModelIndex();
|
||||||
|
|
||||||
|
TreeNode *childNode = static_cast<TreeNode*>(index.internalPointer());
|
||||||
|
const TreeNode *parentNode = childNode->parent();
|
||||||
|
|
||||||
|
if (parentNode == _rootNode.get())
|
||||||
|
return QModelIndex();
|
||||||
|
|
||||||
|
return createIndex(parentNode->row(), 0, parentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DatabaseTree::rowCount(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if(index.column() > 0)return 0;
|
||||||
|
|
||||||
|
TreeNode *node;
|
||||||
|
if(!index.isValid())
|
||||||
|
node = _rootNode.get();
|
||||||
|
else
|
||||||
|
node = static_cast<TreeNode*>(index.internalPointer());
|
||||||
|
|
||||||
|
if(node && node->level() <= _keys.size())
|
||||||
|
return node->childCount();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DatabaseTree::columnCount(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(index);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant DatabaseTree::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if(!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
TreeNode *node = static_cast<TreeNode*>(index.internalPointer());
|
||||||
|
if(node == nullptr)
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
switch(role)
|
||||||
|
{
|
||||||
|
case Qt::FontRole:
|
||||||
|
{
|
||||||
|
if(node->value().toString().isNull())
|
||||||
|
return _italicFont;
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
case Qt::DisplayRole:
|
||||||
|
{
|
||||||
|
QString str = node->value().toString();
|
||||||
|
if(str.isNull())return "NULL";
|
||||||
|
else return str;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseTree::canFetchMore(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if(!parent.isValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
TreeNode *node = static_cast<TreeNode*>(parent.internalPointer());
|
||||||
|
//qDebug() << "Can Fetch more" << node->value();
|
||||||
|
if(node)
|
||||||
|
return !node->filled();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTree::fetchMore(const QModelIndex &parent)
|
||||||
|
{
|
||||||
|
if(!parent.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
TreeNode *node = static_cast<TreeNode*>(parent.internalPointer());
|
||||||
|
//qDebug() << "Fetch more" << node->value();
|
||||||
|
if(node)
|
||||||
|
{
|
||||||
|
fillNode(node);
|
||||||
|
|
||||||
|
if(node->childCount() > 0)
|
||||||
|
{
|
||||||
|
beginInsertRows(parent, 0, node->childCount() - 1);
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant DatabaseTree::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0)
|
||||||
|
return _keys.join('/');
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTree::load()
|
||||||
|
{
|
||||||
|
if(!_loaded)
|
||||||
|
{
|
||||||
|
_loaded = true;
|
||||||
|
setKeys(_keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery DatabaseTree::getGroupQuery(const QString &aggregateFunc) const
|
||||||
|
{
|
||||||
|
QStringList cols;
|
||||||
|
QString join;
|
||||||
|
QString sum;
|
||||||
|
|
||||||
|
for(int i = 0; i < _keys.size(); i++)
|
||||||
|
{
|
||||||
|
join += QString(" LEFT JOIN fits_headers AS h%1 ON f.id = h%1.id_file AND h%1.key = ?").arg(i);
|
||||||
|
if(_keys[i] == "DATE-OBS_YEAR")
|
||||||
|
cols.append(QString("STRFTIME('%Y', h%1.value)").arg(i));
|
||||||
|
else if(_keys[i] == "DATE-OBS_YEAR-MONTH")
|
||||||
|
cols.append(QString("STRFTIME('%Y-%m', h%1.value)").arg(i));
|
||||||
|
else if(_keys[i] == "DATE-OBS_YEAR-MONTH-DAY")
|
||||||
|
cols.append(QString("STRFTIME('%Y-%m-%d', h%1.value)").arg(i));
|
||||||
|
else
|
||||||
|
cols.append(QString("h%1.value").arg(i));
|
||||||
|
|
||||||
|
if(i == _keys.size() - 1)
|
||||||
|
{
|
||||||
|
QString tmp = aggregateFunc + "(" + cols.last() + ")";
|
||||||
|
cols.last() = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList group = cols;
|
||||||
|
group.removeLast();
|
||||||
|
QString sql = "SELECT " + cols.join(',') + " FROM fits_files AS f" + join + " GROUP BY " + group.join(',');
|
||||||
|
|
||||||
|
QSqlQuery query(sql, _database->db());
|
||||||
|
for(auto &val : _keys)
|
||||||
|
{
|
||||||
|
if(val.startsWith("DATE-OBS_"))
|
||||||
|
query.addBindValue("DATE-OBS");
|
||||||
|
else
|
||||||
|
query.addBindValue(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Group query" << sql;
|
||||||
|
if(!query.exec())
|
||||||
|
qWarning() << "Group query failed" << query.lastError();
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTree::prepareQueries()
|
||||||
|
{
|
||||||
|
if(!_loaded)return;
|
||||||
|
|
||||||
|
_queries.clear();
|
||||||
|
|
||||||
|
QString join;
|
||||||
|
QString where;
|
||||||
|
|
||||||
|
for(int i = 0; i < _keys.size(); i++)
|
||||||
|
join += QString(" LEFT JOIN fits_headers AS h%1 ON f.id = h%1.id_file AND h%1.key = ?").arg(i);
|
||||||
|
|
||||||
|
for(int i = 0; i < _keys.size(); i++)
|
||||||
|
{
|
||||||
|
QString sql;
|
||||||
|
QString col = QString("h%1.value").arg(i);
|
||||||
|
if(_keys[i] == "DATE-OBS_YEAR")
|
||||||
|
col = QString("STRFTIME('%Y', h%1.value)").arg(i);
|
||||||
|
else if(_keys[i] == "DATE-OBS_YEAR-MONTH")
|
||||||
|
col = QString("STRFTIME('%Y-%m', h%1.value)").arg(i);
|
||||||
|
else if(_keys[i] == "DATE-OBS_YEAR-MONTH-DAY")
|
||||||
|
col = QString("STRFTIME('%Y-%m-%d', h%1.value)").arg(i);
|
||||||
|
|
||||||
|
sql = QString("SELECT %1 FROM fits_files AS f").arg(col) + join + where + QString(" GROUP BY %1 ORDER BY %1").arg(col);
|
||||||
|
|
||||||
|
qDebug() << "Tree query for" << _keys[i] << sql;
|
||||||
|
QSqlQuery query(sql, _database->db());
|
||||||
|
for(auto &val : _keys)
|
||||||
|
{
|
||||||
|
if(val.startsWith("DATE-OBS_"))
|
||||||
|
query.addBindValue("DATE-OBS");
|
||||||
|
else
|
||||||
|
query.addBindValue(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(where.isEmpty())
|
||||||
|
where += QString(" WHERE %1 IS ?").arg(col);
|
||||||
|
else
|
||||||
|
where += QString(" AND %1 IS ?").arg(col);
|
||||||
|
_queries.append(std::move(query));
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery files("SELECT f.file FROM fits_files AS f" + join + where + " GROUP BY f.id ORDER BY f.file", _database->db());
|
||||||
|
for(auto &val : _keys)
|
||||||
|
{
|
||||||
|
if(val.startsWith("DATE-OBS_"))
|
||||||
|
files.addBindValue("DATE-OBS");
|
||||||
|
else
|
||||||
|
files.addBindValue(val);
|
||||||
|
}
|
||||||
|
qDebug() << files.lastQuery();
|
||||||
|
_queries.append(std::move(files));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTree::fillNode(TreeNode *node)
|
||||||
|
{
|
||||||
|
if(node->filled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
TreeNode *n = node;
|
||||||
|
QVariantList vals;
|
||||||
|
while(n->parent())
|
||||||
|
{
|
||||||
|
vals.prepend(n->value());
|
||||||
|
n = n->parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
int level = vals.size();
|
||||||
|
if(level >= _queries.size())
|
||||||
|
{
|
||||||
|
qWarning() << "Level is too deep";
|
||||||
|
node->fill({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QSqlQuery &q = _queries[level];
|
||||||
|
|
||||||
|
for(int i = 0; i < level; i++)
|
||||||
|
q.bindValue(i + _keys.size(), vals[i]);
|
||||||
|
if(!q.exec())
|
||||||
|
{
|
||||||
|
qWarning() << "Failed to execute query" << q.lastError() << q.lastQuery() << q.boundValues();
|
||||||
|
node->fill({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList list;
|
||||||
|
while(q.next())
|
||||||
|
list.append(q.value(0));
|
||||||
|
|
||||||
|
node->fill(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseTreeView::DatabaseTreeView(Database *database, QWidget *parent) : QWidget(parent)
|
||||||
|
,_database(database)
|
||||||
|
{
|
||||||
|
QVBoxLayout *vlayout = new QVBoxLayout(this);
|
||||||
|
QHBoxLayout *hlayout = new QHBoxLayout(this);
|
||||||
|
|
||||||
|
_model = new DatabaseTree(database, this);
|
||||||
|
_treeView = new QTreeView(this);
|
||||||
|
_treeView->setModel(_model);
|
||||||
|
_treeView->setHeaderHidden(true);
|
||||||
|
|
||||||
|
_tableView = new CopyTableView(this);
|
||||||
|
_sqlModel = new QSqlQueryModel(this);
|
||||||
|
_tableView->setModel(_sqlModel);
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
QStringList filters = settings.value("databasetreeview/filters", QStringList{"OBJECT", "OBJECT/IMAGETYP", "OBJECT/IMAGETYP/FILTER", "OBJECT/IMAGETYP/FILTER/EXPTIME",
|
||||||
|
"IMAGETYP/OBJECT/IMAGETYP/FILTER/EXPTIME", "IMAGETYP/DATE-OBS_YEAR/EXPTIME"}).toStringList();
|
||||||
|
QStringList aggrFuncs = settings.value("databasetreeview/aggrFuncs", QStringList{"", "", "", "SUM", "SUM", "SUM"}).toStringList();
|
||||||
|
int selectedFilter = settings.value("databasetreeview/selectedFilter", 2).toInt();
|
||||||
|
|
||||||
|
_filters = new QComboBox(this);
|
||||||
|
for(int i = 0; i < std::min(filters.size(), aggrFuncs.size()); i++)
|
||||||
|
{
|
||||||
|
_filters->addItem(filters[i] + " " + aggrFuncs[i], QStringList{filters[i], aggrFuncs[i]});
|
||||||
|
}
|
||||||
|
_filters->setCurrentIndex(selectedFilter);
|
||||||
|
connect(_filters, &QComboBox::currentIndexChanged, this, &DatabaseTreeView::filterChanged);
|
||||||
|
filterChanged(_filters->currentIndex());
|
||||||
|
|
||||||
|
QStackedWidget *stackedWidget = new QStackedWidget;
|
||||||
|
stackedWidget->addWidget(_treeView);
|
||||||
|
stackedWidget->addWidget(_tableView);
|
||||||
|
|
||||||
|
QPushButton *addButton = new QPushButton(tr("Add"), this);
|
||||||
|
QPushButton *removeButton = new QPushButton(tr("Remove"), this);
|
||||||
|
QPushButton *treeTableButton = new QPushButton(tr("Tree/Table"), this);
|
||||||
|
treeTableButton->setCheckable(true);
|
||||||
|
connect(treeTableButton, &QPushButton::clicked, [stackedWidget](bool checked){
|
||||||
|
stackedWidget->setCurrentIndex(checked ? 1 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
hlayout->addWidget(_filters, 1);
|
||||||
|
hlayout->addWidget(addButton);
|
||||||
|
hlayout->addWidget(removeButton);
|
||||||
|
hlayout->addWidget(treeTableButton);
|
||||||
|
|
||||||
|
vlayout->addLayout(hlayout);
|
||||||
|
vlayout->addWidget(stackedWidget);
|
||||||
|
|
||||||
|
connect(_treeView, &QTreeView::activated, [this](const QModelIndex &index){
|
||||||
|
if(!_model->hasChildren(index))
|
||||||
|
{
|
||||||
|
QString path = _model->data(index).toString();
|
||||||
|
emit loadFile(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(addButton, &QPushButton::clicked, this, &DatabaseTreeView::addFilter);
|
||||||
|
connect(removeButton, &QPushButton::clicked, this, &DatabaseTreeView::removeFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseTreeView::~DatabaseTreeView()
|
||||||
|
{
|
||||||
|
QStringList filters;
|
||||||
|
QStringList aggrFuncs;
|
||||||
|
for(int i = 0; i < _filters->count(); i++)
|
||||||
|
{
|
||||||
|
QStringList data = _filters->itemData(i).toStringList();
|
||||||
|
filters.append(data[0]);
|
||||||
|
aggrFuncs.append(data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue("databasetreeview/filters", filters);
|
||||||
|
settings.setValue("databasetreeview/aggrFuncs", aggrFuncs);
|
||||||
|
settings.setValue("databasetreeview/selectedFilter", _filters->currentIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTreeView::addFilter()
|
||||||
|
{
|
||||||
|
QStringList keywords = _database->getFitsKeywords();
|
||||||
|
QStringList data = _filters->currentData().toStringList();
|
||||||
|
DatabaseTreeSettings settings(data, keywords, this);
|
||||||
|
int result = settings.exec();
|
||||||
|
if(result == QDialog::Accepted)
|
||||||
|
{
|
||||||
|
QString keywords = settings.keywords();
|
||||||
|
QString aggrFunc = settings.aggregrationFunc();
|
||||||
|
QString text = keywords + " " + aggrFunc;
|
||||||
|
int idx = _filters->findText(text);
|
||||||
|
if(idx == -1)
|
||||||
|
{
|
||||||
|
_filters->addItem(text, QStringList{keywords, aggrFunc});
|
||||||
|
_filters->setCurrentText(text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_filters->setCurrentIndex(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTreeView::removeFilter()
|
||||||
|
{
|
||||||
|
if(_filters->count() > 1)
|
||||||
|
_filters->removeItem(_filters->currentIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTreeView::filterChanged(int index)
|
||||||
|
{
|
||||||
|
QStringList data = _filters->itemData(index).toStringList();
|
||||||
|
QStringList keys = data[0].split('/');
|
||||||
|
_model->setKeys(keys);
|
||||||
|
setQuery(data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTreeView::visible(bool visible)
|
||||||
|
{
|
||||||
|
if(visible && !_loaded)
|
||||||
|
{
|
||||||
|
_loaded = true;
|
||||||
|
_model->load();
|
||||||
|
QStringList data = _filters->currentData().toStringList();
|
||||||
|
setQuery(data[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseTreeView::setQuery(const QString &func)
|
||||||
|
{
|
||||||
|
QStringList keys = _model->keys();
|
||||||
|
int i = 0;
|
||||||
|
_sqlModel->setQuery(_model->getGroupQuery(func));
|
||||||
|
if(!func.isEmpty())
|
||||||
|
{
|
||||||
|
QString tmp = func + "(" + keys.last() + ")";
|
||||||
|
keys.last() = tmp;
|
||||||
|
}
|
||||||
|
for(auto &key : keys)
|
||||||
|
_sqlModel->setHeaderData(i++, Qt::Horizontal, key);
|
||||||
|
|
||||||
|
_tableView->resizeColumnsToContents();
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#ifndef DATABASETREE_H
|
||||||
|
#define DATABASETREE_H
|
||||||
|
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QFont>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QSqlQueryModel>
|
||||||
|
#include <QTableView>
|
||||||
|
#include <QTreeView>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class Database;
|
||||||
|
class TreeNode;
|
||||||
|
|
||||||
|
class DatabaseTreeSettings : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit DatabaseTreeSettings(const QStringList &data, QStringList keywords, QWidget *parent = nullptr);
|
||||||
|
QString keywords() const;
|
||||||
|
QString aggregrationFunc() const;
|
||||||
|
public slots:
|
||||||
|
void acceptButton();
|
||||||
|
private:
|
||||||
|
QVector<QComboBox*> _keywordsSelect;
|
||||||
|
QComboBox *_aggregateFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DatabaseTree : public QAbstractItemModel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit DatabaseTree(Database *database, QObject *parent = nullptr);
|
||||||
|
void setKeys(const QStringList &keys);
|
||||||
|
QStringList keys() const;
|
||||||
|
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
|
||||||
|
QModelIndex parent(const QModelIndex &index) const override;
|
||||||
|
int rowCount(const QModelIndex &index) const override;
|
||||||
|
int columnCount(const QModelIndex &index) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
bool canFetchMore(const QModelIndex &parent) const override;
|
||||||
|
void fetchMore(const QModelIndex &parent) override;
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||||
|
void load();
|
||||||
|
QSqlQuery getGroupQuery(const QString &aggregateFunc) const;
|
||||||
|
private:
|
||||||
|
void prepareQueries();
|
||||||
|
void fillNode(TreeNode *node);
|
||||||
|
Database *_database = nullptr;
|
||||||
|
std::unique_ptr<TreeNode> _rootNode;
|
||||||
|
QVector<QSqlQuery> _queries;
|
||||||
|
QStringList _keys;
|
||||||
|
QFont _italicFont;
|
||||||
|
bool _loaded = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DatabaseTreeView : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit DatabaseTreeView(Database *database, QWidget *parent = nullptr);
|
||||||
|
virtual ~DatabaseTreeView();
|
||||||
|
public slots:
|
||||||
|
void addFilter();
|
||||||
|
void removeFilter();
|
||||||
|
void filterChanged(int index);
|
||||||
|
void visible(bool visible);
|
||||||
|
private:
|
||||||
|
void setQuery(const QString &func);
|
||||||
|
signals:
|
||||||
|
void loadFile(const QString &file);
|
||||||
|
private:
|
||||||
|
QComboBox *_filters = nullptr;
|
||||||
|
QTreeView *_treeView = nullptr;
|
||||||
|
QTableView *_tableView = nullptr;
|
||||||
|
DatabaseTree *_model = nullptr;
|
||||||
|
QSqlQueryModel *_sqlModel = nullptr;
|
||||||
|
Database *_database = nullptr;
|
||||||
|
bool _loaded = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DATABASETREE_H
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Dialog</class>
|
||||||
|
<widget class="QDialog" name="Dialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>511</width>
|
||||||
|
<height>487</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>120</x>
|
||||||
|
<y>390</y>
|
||||||
|
<width>341</width>
|
||||||
|
<height>32</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QComboBox" name="comboBox">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>60</x>
|
||||||
|
<y>30</y>
|
||||||
|
<width>86</width>
|
||||||
|
<height>26</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="QLineEdit" name="lineEdit">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>180</x>
|
||||||
|
<y>30</y>
|
||||||
|
<width>113</width>
|
||||||
|
<height>26</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
||||||
@@ -9,7 +9,11 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QContextMenuEvent>
|
#include <QContextMenuEvent>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QMimeData>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include "batchprocessing.h"
|
||||||
|
|
||||||
const QStringList DEFAULT_COLUMNS = {"EXPTIME", "OBJECT", "RA", "DEC"};
|
const QStringList DEFAULT_COLUMNS = {"EXPTIME", "OBJECT", "RA", "DEC"};
|
||||||
|
|
||||||
@@ -156,32 +160,67 @@ void FITSFileModel::filesUnmarked(const QModelIndexList &indexes)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FITSFileModel::load()
|
||||||
|
{
|
||||||
|
if(!m_loaded)
|
||||||
|
{
|
||||||
|
m_loaded = true;
|
||||||
|
prepareQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void FITSFileModel::prepareQuery()
|
void FITSFileModel::prepareQuery()
|
||||||
{
|
{
|
||||||
|
if(!m_loaded)return;
|
||||||
|
|
||||||
QString cols;
|
QString cols;
|
||||||
QString join;
|
QString join;
|
||||||
QStringList where;
|
QStringList where;
|
||||||
QString sql = m_columns.size() ? "SELECT f.file," : "SELECT f.file";
|
QString sql = m_columns.size() ? "SELECT f.file," : "SELECT f.file";
|
||||||
|
QVariantList bindValues;
|
||||||
|
QVariantList bindValuesJoin;
|
||||||
for(int i=0; i<m_value.size(); i++)
|
for(int i=0; i<m_value.size(); i++)
|
||||||
{
|
{
|
||||||
if(m_key[i] == "file")
|
if(m_key[i] == "file")
|
||||||
where.append(QString(" f.file LIKE '%1' ").arg(m_value[i]));
|
{
|
||||||
|
where.append(" f.file LIKE ? ");
|
||||||
|
bindValues.append(m_value[i]);
|
||||||
|
}
|
||||||
else if(m_key[i] == "RA pos")
|
else if(m_key[i] == "RA pos")
|
||||||
where.append(QString(" %1 BETWEEN f.minRa AND f.maxRa ").arg(RA(m_value[i])));
|
{
|
||||||
|
where.append(" ? BETWEEN f.minRa AND f.maxRa ");
|
||||||
|
bindValues.append(RA(m_value[i]));
|
||||||
|
}
|
||||||
else if(m_key[i] == "DEC pos")
|
else if(m_key[i] == "DEC pos")
|
||||||
where.append(QString(" %1 BETWEEN f.minDec AND f.maxDec ").arg(DEC(m_value[i])));
|
{
|
||||||
|
where.append(" ? BETWEEN f.minDec AND f.maxDec ");
|
||||||
|
bindValues.append(DEC(m_value[i]));
|
||||||
|
}
|
||||||
else if(m_key[i] == "RA range")
|
else if(m_key[i] == "RA range")
|
||||||
where.append(QString(" crVal1 BETWEEN %1 AND %2 ").arg(RA(m_value[i])).arg(RA(m_limit[i])));
|
{
|
||||||
|
where.append(" crVal1 BETWEEN ? AND ? ");
|
||||||
|
bindValues.append(RA(m_value[i]));
|
||||||
|
bindValues.append(RA(m_limit[i]));
|
||||||
|
}
|
||||||
else if(m_key[i] == "DEC range")
|
else if(m_key[i] == "DEC range")
|
||||||
where.append(QString(" crVal2 BETWEEN %1 AND %2 ").arg(DEC(m_value[i])).arg(DEC(m_limit[i])));
|
{
|
||||||
|
where.append(" crVal2 BETWEEN ? AND ? ");
|
||||||
|
bindValues.append(DEC(m_value[i]));
|
||||||
|
bindValues.append(DEC(m_limit[i]));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
join += QString(" JOIN fits_headers AS s%1 ON f.id=s%1.id_file AND s%1.key='%2' AND s%1.value LIKE '%3'").arg(i).arg(m_key[i]).arg(m_value[i]);
|
{
|
||||||
|
join += QString(" JOIN fits_headers AS s%1 ON f.id=s%1.id_file AND s%1.key=? AND s%1.value LIKE ? ").arg(i);
|
||||||
|
bindValuesJoin.append(m_key[i]);
|
||||||
|
bindValuesJoin.append(m_value[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int i=0;
|
int i=0;
|
||||||
for(auto &column : m_columns)
|
for(auto &column : m_columns)
|
||||||
{
|
{
|
||||||
cols += QString("GROUP_CONCAT(h%1.value) AS h%1_value,").arg(i);
|
cols += QString("GROUP_CONCAT(h%1.value) AS h%1_value,").arg(i);
|
||||||
join += QString(" LEFT JOIN fits_headers AS h%1 ON f.id=h%1.id_file AND h%1.key='%2'").arg(i).arg(column);
|
join += QString(" LEFT JOIN fits_headers AS h%1 ON f.id=h%1.id_file AND h%1.key=?").arg(i);
|
||||||
|
bindValuesJoin.append(column);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
cols.chop(1);
|
cols.chop(1);
|
||||||
@@ -190,7 +229,19 @@ void FITSFileModel::prepareQuery()
|
|||||||
sql += join;
|
sql += join;
|
||||||
if(!where.isEmpty())sql += " WHERE " + where.join("AND");
|
if(!where.isEmpty())sql += " WHERE " + where.join("AND");
|
||||||
sql += " GROUP BY f.id" + m_sort;
|
sql += " GROUP BY f.id" + m_sort;
|
||||||
setQuery(sql);
|
|
||||||
|
QSqlQuery query(m_database->db());
|
||||||
|
query.prepare(sql);
|
||||||
|
for(auto &val : bindValuesJoin)
|
||||||
|
query.addBindValue(val);
|
||||||
|
for(auto &val : bindValues)
|
||||||
|
query.addBindValue(val);
|
||||||
|
|
||||||
|
if(!query.exec())
|
||||||
|
qWarning() << "Failed to exectute query" << query.lastQuery() << bindValuesJoin << bindValues;
|
||||||
|
else
|
||||||
|
setQuery(std::move(query));
|
||||||
|
|
||||||
setHeaderData(0, Qt::Horizontal, tr("File name"));
|
setHeaderData(0, Qt::Horizontal, tr("File name"));
|
||||||
i = 1;
|
i = 1;
|
||||||
for(auto &column : m_columns)
|
for(auto &column : m_columns)
|
||||||
@@ -205,7 +256,7 @@ void FITSFileModel::prepareQuery()
|
|||||||
m_markedFiles = QSet<QString>(list.begin(), list.end());
|
m_markedFiles = QSet<QString>(list.begin(), list.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseTableView::DatabaseTableView(QWidget *parent) : QTableView(parent)
|
DatabaseTableView::DatabaseTableView(QWidget *parent) : CopyTableView(parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +265,9 @@ void DatabaseTableView::contextMenuEvent(QContextMenuEvent *event)
|
|||||||
QMenu menu;
|
QMenu menu;
|
||||||
QAction *mark = menu.addAction(tr("Mark"));
|
QAction *mark = menu.addAction(tr("Mark"));
|
||||||
QAction *unmark = menu.addAction(tr("Unmark"));
|
QAction *unmark = menu.addAction(tr("Unmark"));
|
||||||
|
QAction *open = menu.addAction(tr("Open"));
|
||||||
|
QAction *openDirAction = menu.addAction(tr("Open file location"));
|
||||||
|
QAction *copyPath = menu.addAction(tr("Copy files"));
|
||||||
|
|
||||||
QAction *a = menu.exec(event->globalPos());
|
QAction *a = menu.exec(event->globalPos());
|
||||||
if(a == nullptr)
|
if(a == nullptr)
|
||||||
@@ -225,7 +279,26 @@ void DatabaseTableView::contextMenuEvent(QContextMenuEvent *event)
|
|||||||
emit filesMarked(indexes);
|
emit filesMarked(indexes);
|
||||||
else if(a == unmark)
|
else if(a == unmark)
|
||||||
emit filesUnmarked(indexes);
|
emit filesUnmarked(indexes);
|
||||||
|
else if(a == open)
|
||||||
|
emit openFile(indexes);
|
||||||
|
else if(a == openDirAction)
|
||||||
|
emit openDir(indexes);
|
||||||
|
else if(a == copyPath)
|
||||||
|
{
|
||||||
|
QStringList paths;
|
||||||
|
QList<QUrl> urls;
|
||||||
|
for(auto &index : indexes)
|
||||||
|
{
|
||||||
|
QString path = index.siblingAtColumn(0).data().toString();
|
||||||
|
paths.append(path);
|
||||||
|
urls.append(QUrl::fromLocalFile(path));
|
||||||
|
}
|
||||||
|
QMimeData *data = new QMimeData;
|
||||||
|
data->setUrls(urls);
|
||||||
|
data->setText(paths.join('\n'));
|
||||||
|
QClipboard *clipboard = QGuiApplication::clipboard();
|
||||||
|
clipboard->setMimeData(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent)
|
DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent)
|
||||||
@@ -270,6 +343,17 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
|
|||||||
m_database->unmark(files);
|
m_database->unmark(files);
|
||||||
m_model->filesUnmarked(indexes);
|
m_model->filesUnmarked(indexes);
|
||||||
});
|
});
|
||||||
|
connect(m_tableView, &DatabaseTableView::openFile, [this](QModelIndexList indexes){
|
||||||
|
if(indexes.size())
|
||||||
|
emit loadFile(m_model->data(indexes.front().siblingAtColumn(0)).toString());
|
||||||
|
});
|
||||||
|
connect(m_tableView, &DatabaseTableView::openDir, [this](QModelIndexList indexes){
|
||||||
|
if(indexes.size())
|
||||||
|
{
|
||||||
|
QFileInfo info(m_model->data(indexes.front().siblingAtColumn(0)).toString());
|
||||||
|
openDir(info.absolutePath());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
auto addFilterItems = [](QComboBox *combobox, const QStringList &fitsKeywords)
|
auto addFilterItems = [](QComboBox *combobox, const QStringList &fitsKeywords)
|
||||||
{
|
{
|
||||||
@@ -283,12 +367,13 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
|
|||||||
};
|
};
|
||||||
|
|
||||||
QStringList fitsKeywords = m_database->getFitsKeywords();
|
QStringList fitsKeywords = m_database->getFitsKeywords();
|
||||||
|
QStringList filterKey = settings.value("databaseview/filterKey", QStringList{"file", "file", "file"}).toStringList();
|
||||||
for(int i=0; i<3; i++)
|
for(int i=0; i<3; i++)
|
||||||
{
|
{
|
||||||
m_filterKeyword[i] = new QComboBox(this);
|
m_filterKeyword[i] = new QComboBox(this);
|
||||||
m_filterKeyword[i]->setMaximumWidth(300);
|
m_filterKeyword[i]->setMaximumWidth(300);
|
||||||
addFilterItems(m_filterKeyword[i], fitsKeywords);
|
addFilterItems(m_filterKeyword[i], fitsKeywords);
|
||||||
|
m_filterKeyword[i]->setCurrentText(filterKey[i]);
|
||||||
|
|
||||||
m_search[i] = new QLineEdit(this);
|
m_search[i] = new QLineEdit(this);
|
||||||
m_search[i]->setPlaceholderText(tr("Text to search, you can % as wildcard"));
|
m_search[i]->setPlaceholderText(tr("Text to search, you can % as wildcard"));
|
||||||
@@ -308,7 +393,7 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
|
|||||||
}
|
}
|
||||||
|
|
||||||
QPushButton *filterButton = new QPushButton(tr("Filter"), this);
|
QPushButton *filterButton = new QPushButton(tr("Filter"), this);
|
||||||
connect(filterButton, SIGNAL(pressed()), this, SLOT(applyFilter()));
|
connect(filterButton, &QPushButton::pressed, this, &DataBaseView::applyFilter);
|
||||||
hlayout->addWidget(filterButton);
|
hlayout->addWidget(filterButton);
|
||||||
|
|
||||||
connect(m_database, &Database::databaseChanged, [this, &addFilterItems](){
|
connect(m_database, &Database::databaseChanged, [this, &addFilterItems](){
|
||||||
@@ -322,8 +407,13 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
|
|||||||
|
|
||||||
DataBaseView::~DataBaseView()
|
DataBaseView::~DataBaseView()
|
||||||
{
|
{
|
||||||
|
QStringList filterKey;
|
||||||
|
for(int i = 0; i < 3; i++)
|
||||||
|
filterKey.append(m_filterKeyword[i]->currentText());
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.setValue("databaseview/header", m_tableView->horizontalHeader()->saveState());
|
settings.setValue("databaseview/header", m_tableView->horizontalHeader()->saveState());
|
||||||
|
settings.setValue("databaseview/filterKey", filterKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataBaseView::selectColumns()
|
void DataBaseView::selectColumns()
|
||||||
@@ -375,6 +465,7 @@ bool DataBaseView::exportCSV(const QString &path)
|
|||||||
if(!csv.open(QIODevice::WriteOnly | QIODevice::Text))
|
if(!csv.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
m_model->load();
|
||||||
QSqlQuery sql(m_model->query().lastQuery());
|
QSqlQuery sql(m_model->query().lastQuery());
|
||||||
int colCount = m_model->columnCount();
|
int colCount = m_model->columnCount();
|
||||||
QStringList header;
|
QStringList header;
|
||||||
@@ -403,3 +494,42 @@ bool DataBaseView::exportCSV(const QString &path)
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DataBaseView::visible(bool visible)
|
||||||
|
{
|
||||||
|
if(visible)m_model->load();
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyTableView::CopyTableView(QWidget *parent) : QTableView(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CopyTableView::keyPressEvent(QKeyEvent *event)
|
||||||
|
{
|
||||||
|
if(event->matches(QKeySequence::Copy))
|
||||||
|
{
|
||||||
|
QModelIndexList list = selectedIndexes();
|
||||||
|
QString table;
|
||||||
|
if(list.size() == 0)return;
|
||||||
|
|
||||||
|
int row = list.first().row();
|
||||||
|
int col = list.first().column();
|
||||||
|
for(auto &index : list)
|
||||||
|
{
|
||||||
|
if(row != index.row())
|
||||||
|
table.append('\n');
|
||||||
|
else if(col != index.column())
|
||||||
|
table.append('\t');
|
||||||
|
|
||||||
|
table.append(index.data().toString());
|
||||||
|
row = index.row();
|
||||||
|
col = index.column();
|
||||||
|
}
|
||||||
|
qApp->clipboard()->setText(table);
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QTableView::keyPressEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ class FITSFileModel : public QSqlQueryModel
|
|||||||
QStringList m_limit;
|
QStringList m_limit;
|
||||||
QSet<QString> m_markedFiles;
|
QSet<QString> m_markedFiles;
|
||||||
Database *m_database;
|
Database *m_database;
|
||||||
|
bool m_loaded = false;
|
||||||
public:
|
public:
|
||||||
explicit FITSFileModel(Database *database, QObject *parent = nullptr);
|
explicit FITSFileModel(Database *database, QObject *parent = nullptr);
|
||||||
void sort(int column, Qt::SortOrder order) override;
|
void sort(int column, Qt::SortOrder order) override;
|
||||||
@@ -38,11 +39,20 @@ public:
|
|||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
void filesMarked(const QModelIndexList &indexes);
|
void filesMarked(const QModelIndexList &indexes);
|
||||||
void filesUnmarked(const QModelIndexList &indexes);
|
void filesUnmarked(const QModelIndexList &indexes);
|
||||||
|
void load();
|
||||||
protected:
|
protected:
|
||||||
void prepareQuery();
|
void prepareQuery();
|
||||||
};
|
};
|
||||||
|
|
||||||
class DatabaseTableView : public QTableView
|
class CopyTableView : public QTableView
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit CopyTableView(QWidget *parent = nullptr);
|
||||||
|
void keyPressEvent(QKeyEvent *event);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DatabaseTableView : public CopyTableView
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
@@ -52,6 +62,8 @@ protected:
|
|||||||
signals:
|
signals:
|
||||||
void filesMarked(QModelIndexList indexes);
|
void filesMarked(QModelIndexList indexes);
|
||||||
void filesUnmarked(QModelIndexList indexes);
|
void filesUnmarked(QModelIndexList indexes);
|
||||||
|
void openFile(QModelIndexList indexes);
|
||||||
|
void openDir(QModelIndexList indexes);
|
||||||
};
|
};
|
||||||
|
|
||||||
class DataBaseView : public QWidget
|
class DataBaseView : public QWidget
|
||||||
@@ -72,6 +84,7 @@ public slots:
|
|||||||
void itemActivated(const QModelIndex &index);
|
void itemActivated(const QModelIndex &index);
|
||||||
void applyFilter();
|
void applyFilter();
|
||||||
bool exportCSV(const QString &path);
|
bool exportCSV(const QString &path);
|
||||||
|
void visible(bool visible);
|
||||||
signals:
|
signals:
|
||||||
void loadFile(QString file);
|
void loadFile(QString file);
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,766 @@
|
|||||||
|
#include "filemanager.h"
|
||||||
|
#include "ui_filemanager.h"
|
||||||
|
#include "ui_fitskeyword.h"
|
||||||
|
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QMimeData>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include "loadimage.h"
|
||||||
|
|
||||||
|
class FileTimes
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit FileTimes(const QString &path)
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
#ifndef Q_OS_WIN
|
||||||
|
birthTime = file.fileTime(QFileDevice::FileBirthTime);
|
||||||
|
#endif
|
||||||
|
modificationTime = file.fileTime(QFileDevice::FileModificationTime);
|
||||||
|
accessTime = file.fileTime(QFileDevice::FileAccessTime);
|
||||||
|
}
|
||||||
|
void apply(const QString &path)
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
if(file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::ExistingOnly))
|
||||||
|
{
|
||||||
|
#ifndef Q_OS_WIN // Only windows allow changing birth time
|
||||||
|
file.setFileTime(birthTime, QFileDevice::FileBirthTime);
|
||||||
|
#endif
|
||||||
|
file.setFileTime(accessTime, QFileDevice::FileAccessTime);
|
||||||
|
file.setFileTime(modificationTime, QFileDevice::FileModificationTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
QDateTime birthTime;
|
||||||
|
QDateTime modificationTime;
|
||||||
|
QDateTime accessTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
FileTransfer::FileTransfer(FileManager *fm) :
|
||||||
|
_fm(fm)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FileTransfer::~FileTransfer()
|
||||||
|
{
|
||||||
|
_run = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileTransfer::copy(const QStringList &src, const QString &dst)
|
||||||
|
{
|
||||||
|
_run = true;
|
||||||
|
perform(src, dst, true);
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileTransfer::move(const QStringList &src, const QString &dst)
|
||||||
|
{
|
||||||
|
_run = true;
|
||||||
|
perform(src, dst, false);
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileTransfer::cancel()
|
||||||
|
{
|
||||||
|
_run = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileTransfer::perform(const QStringList &src, const QString &dst, bool copy)
|
||||||
|
{
|
||||||
|
QDir dstDir(dst);
|
||||||
|
if(!dstDir.exists())
|
||||||
|
{
|
||||||
|
emit error(tr("Error"), tr("Destination directory %1 doesn't exists").arg(dstDir.absolutePath()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<Action> actions;
|
||||||
|
QStringList dirs;
|
||||||
|
|
||||||
|
emit progress(0);
|
||||||
|
|
||||||
|
for(const QString &i : src)
|
||||||
|
{
|
||||||
|
QFileInfo srcInfo(i);
|
||||||
|
if(srcInfo.absolutePath() == dst || dst.startsWith(srcInfo.absoluteFilePath()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(srcInfo.isDir())
|
||||||
|
{
|
||||||
|
QDir srcDir(i);
|
||||||
|
//qDebug() << "dir" << srcInfo.absoluteFilePath() << srcInfo.fileName();
|
||||||
|
if(!copy && !dstDir.exists(srcInfo.fileName()))
|
||||||
|
{
|
||||||
|
if(QFile::rename(srcInfo.absoluteFilePath(), dstDir.absoluteFilePath(srcInfo.fileName())))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
actions.append({srcInfo.absoluteFilePath(), srcInfo.fileName(), true});
|
||||||
|
if(!copy)dirs.prepend(srcInfo.absoluteFilePath());
|
||||||
|
QDirIterator it(i, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||||
|
while(it.hasNext())
|
||||||
|
{
|
||||||
|
QFileInfo info = it.nextFileInfo();
|
||||||
|
if(info.fileName() == "." || info.fileName() == "..")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QString relativePath = srcDir.dirName() + "/" + srcDir.relativeFilePath(info.absoluteFilePath());
|
||||||
|
if(info.isDir())
|
||||||
|
{
|
||||||
|
actions.append({"", relativePath, true});
|
||||||
|
if(!copy)dirs.prepend(info.absoluteFilePath());
|
||||||
|
//qDebug() << "dir" << info.absoluteFilePath() << relativePath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actions.append({info.absoluteFilePath(), dstDir.absoluteFilePath(relativePath), false});
|
||||||
|
//qDebug() << "file" << info.absoluteFilePath() << dstDir.absoluteFilePath(relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actions.append({srcInfo.absoluteFilePath(), dstDir.absoluteFilePath(srcInfo.fileName())});
|
||||||
|
//qDebug() << "file" << srcInfo.absoluteFilePath() << dstDir.absoluteFilePath(srcInfo.fileName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool overwriteAll = false;
|
||||||
|
bool skipAll = false;
|
||||||
|
int total = actions.size();
|
||||||
|
int i = 0;
|
||||||
|
for(auto &a : actions)
|
||||||
|
{
|
||||||
|
if(!_run)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(a.dir)
|
||||||
|
{
|
||||||
|
dstDir.mkpath(a.dst);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QFileInfo dstInfo(a.dst);
|
||||||
|
if(dstInfo.exists())
|
||||||
|
{
|
||||||
|
if(overwriteAll)
|
||||||
|
{
|
||||||
|
QFile::remove(dstInfo.absoluteFilePath());
|
||||||
|
}
|
||||||
|
else if(skipAll)
|
||||||
|
{
|
||||||
|
emit progress(i++ * 100 / total);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QMessageBox::StandardButton ret;
|
||||||
|
QMetaObject::invokeMethod(_fm, "overwrite", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QMessageBox::StandardButton, ret), Q_ARG(QString, dstInfo.fileName()));
|
||||||
|
switch(ret)
|
||||||
|
{
|
||||||
|
case QMessageBox::YesToAll:
|
||||||
|
overwriteAll = true;//break; is intentionally missing
|
||||||
|
case QMessageBox::Yes:
|
||||||
|
QFile::remove(dstInfo.absoluteFilePath());
|
||||||
|
break;
|
||||||
|
case QMessageBox::NoToAll:
|
||||||
|
skipAll = true;//break; is intentionally missing
|
||||||
|
case QMessageBox::No:
|
||||||
|
emit progress(i++ * 100 / total);
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
case QMessageBox::Cancel:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FileTimes t(a.src);
|
||||||
|
if(copy)
|
||||||
|
{
|
||||||
|
if(!QFile::copy(a.src, a.dst))
|
||||||
|
{
|
||||||
|
emit error(tr("Copy failed"), tr("Failed to copy file %1 to %2").arg(a.src).arg(a.dst));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!QFile::rename(a.src, a.dst))
|
||||||
|
{
|
||||||
|
emit error(tr("Move failed"), tr("Failed to move file %1 to %2").arg(a.src).arg(a.dst));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.apply(a.dst);
|
||||||
|
}
|
||||||
|
emit progress(i++ * 100 / total);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!copy)
|
||||||
|
{
|
||||||
|
for(const QString &d : dirs)
|
||||||
|
{
|
||||||
|
QDir dir(d);
|
||||||
|
if(dir.isEmpty())
|
||||||
|
dir.removeRecursively();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PathTabBar::PathTabBar(const QStringList &tabs) :
|
||||||
|
_tabs(tabs)
|
||||||
|
{
|
||||||
|
setTabsClosable(true);
|
||||||
|
setExpanding(false);
|
||||||
|
|
||||||
|
for(auto &t : _tabs)
|
||||||
|
{
|
||||||
|
QDir dir(t);
|
||||||
|
int i = addTab(tabName(t));
|
||||||
|
setTabToolTip(i, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(this, &QTabBar::currentChanged, [this](int index){
|
||||||
|
QString path = _tabs.at(index);
|
||||||
|
emit pathChanged(path);
|
||||||
|
});
|
||||||
|
connect(this, &QTabBar::tabCloseRequested, [this](int index){
|
||||||
|
if(_tabs.size() >= 2)
|
||||||
|
{
|
||||||
|
_tabs.remove(index);
|
||||||
|
removeTab(index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(this, &QTabBar::currentChanged, [this](int index){
|
||||||
|
emit tabChanged(_tabs[index]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QHBoxLayout *PathTabBar::createLayout()
|
||||||
|
{
|
||||||
|
QHBoxLayout *hlayout = new QHBoxLayout();
|
||||||
|
|
||||||
|
hlayout->addWidget(this);
|
||||||
|
hlayout->addStretch(2);
|
||||||
|
QPushButton *addButton = new QPushButton("+");
|
||||||
|
connect(addButton, &QPushButton::clicked, [this](){
|
||||||
|
QString path = _tabs[currentIndex()];
|
||||||
|
_tabs.append(path);
|
||||||
|
int i = addTab(tabName(path));
|
||||||
|
setTabToolTip(i, path);
|
||||||
|
});
|
||||||
|
hlayout->addWidget(addButton);
|
||||||
|
return hlayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList &PathTabBar::tabPaths() const
|
||||||
|
{
|
||||||
|
return _tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PathTabBar::currentTabPath() const
|
||||||
|
{
|
||||||
|
int index = std::clamp(currentIndex(), 0, (int)_tabs.size());
|
||||||
|
return _tabs[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void PathTabBar::pathChanged(const QString &path)
|
||||||
|
{
|
||||||
|
QDir dir(path);
|
||||||
|
int index = currentIndex();
|
||||||
|
setTabText(index, tabName(path));
|
||||||
|
setTabToolTip(index, path);
|
||||||
|
_tabs[index] = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PathTabBar::tabName(const QString &path)
|
||||||
|
{
|
||||||
|
QDir dir(path);
|
||||||
|
if(dir.dirName().isEmpty())
|
||||||
|
return path;
|
||||||
|
else
|
||||||
|
return dir.dirName();
|
||||||
|
}
|
||||||
|
|
||||||
|
FITSSelection::FITSSelection(const QStringList &keywords, QWidget *parent) : QDialog(parent)
|
||||||
|
,ui(new Ui::FITSKeyword)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
connect(ui->addButton, &QPushButton::clicked, [this](){
|
||||||
|
auto item = ui->keywordList->findItems(ui->keyword->text(), Qt::MatchFixedString | Qt::MatchCaseSensitive);
|
||||||
|
if(item.size())return;
|
||||||
|
ui->keywordList->addItem(ui->keyword->text());
|
||||||
|
});
|
||||||
|
connect(ui->removeButton, &QPushButton::clicked, [this](){
|
||||||
|
auto items = ui->keywordList->selectedItems();
|
||||||
|
for(auto item : items)
|
||||||
|
delete item;
|
||||||
|
});
|
||||||
|
|
||||||
|
ui->keywordList->addItems(keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
FITSSelection::~FITSSelection()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList FITSSelection::FITSKeywords() const
|
||||||
|
{
|
||||||
|
QStringList keywords;
|
||||||
|
for(int i = 0; i < ui->keywordList->count(); i++)
|
||||||
|
keywords.append(ui->keywordList->item(i)->text());
|
||||||
|
|
||||||
|
return keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileManager::FileManager(const QSet<QString> &openFilter, QWidget *parent) : QMainWindow(parent)
|
||||||
|
,ui(new Ui::FileManager)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
QStringList standardLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
|
||||||
|
QString picturesPath;
|
||||||
|
if(standardLocations.size())
|
||||||
|
picturesPath = standardLocations.first();
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
QStringList leftTabs = settings.value("filemanager/leftTabPaths", picturesPath).toStringList();
|
||||||
|
QStringList rightTabs = settings.value("filemanager/rightTabPaths", picturesPath).toStringList();
|
||||||
|
if(leftTabs.empty())leftTabs.append(picturesPath);
|
||||||
|
if(rightTabs.empty())rightTabs.append(picturesPath);
|
||||||
|
|
||||||
|
ui->leftTab->setOpenFilter(openFilter);
|
||||||
|
ui->rightTab->setOpenFilter(openFilter);
|
||||||
|
|
||||||
|
_rightTabBar = new PathTabBar(rightTabs);
|
||||||
|
ui->rightLayout->insertLayout(0, _rightTabBar->createLayout());
|
||||||
|
connect(_rightTabBar, &PathTabBar::tabChanged, ui->rightTab, &DirView::setDir);
|
||||||
|
|
||||||
|
_leftTabBar = new PathTabBar(leftTabs);
|
||||||
|
ui->leftLayout->insertLayout(0, _leftTabBar->createLayout());
|
||||||
|
connect(_leftTabBar, &PathTabBar::tabChanged, ui->leftTab, &DirView::setDir);
|
||||||
|
|
||||||
|
connect(ui->leftTab, &DirView::dirChanged, ui->leftPath, &QLineEdit::setText);
|
||||||
|
connect(ui->leftTab, &DirView::dirChanged, _leftTabBar, &PathTabBar::pathChanged);
|
||||||
|
connect(ui->rightTab, &DirView::dirChanged, ui->rightPath, &QLineEdit::setText);
|
||||||
|
connect(ui->rightTab, &DirView::dirChanged, _rightTabBar, &PathTabBar::pathChanged);
|
||||||
|
connect(ui->leftTab, &DirView::openFile, this, &FileManager::openFile);
|
||||||
|
connect(ui->rightTab, &DirView::openFile, this, &FileManager::openFile);
|
||||||
|
connect(ui->leftTab, &DirView::filesAction, this, &FileManager::copyMoveFiles, Qt::QueuedConnection);
|
||||||
|
connect(ui->rightTab, &DirView::filesAction, this, &FileManager::copyMoveFiles, Qt::QueuedConnection);
|
||||||
|
connect(ui->actionLoad_FITS_keywordsLeft, &QAction::toggled, ui->leftTab, &DirView::loadFitsKeywords);
|
||||||
|
connect(ui->actionLoad_FITS_keywordsRight, &QAction::toggled, ui->rightTab, &DirView::loadFitsKeywords);
|
||||||
|
|
||||||
|
ui->leftTab->setDir(_leftTabBar->currentTabPath());
|
||||||
|
ui->leftTab->setFITSKeywords(settings.value("filemanager/leftFitsKeywords", QStringList("OBJECT")).toStringList());
|
||||||
|
ui->leftTab->header()->restoreState(settings.value("filemanager/leftTabHeader").toByteArray());
|
||||||
|
ui->rightTab->setDir(_rightTabBar->currentTabPath());
|
||||||
|
ui->rightTab->setFITSKeywords(settings.value("filemanager/rightFitsKeywords", QStringList("OBJECT")).toStringList());
|
||||||
|
ui->rightTab->header()->restoreState(settings.value("filemanager/rightTabHeader").toByteArray());
|
||||||
|
|
||||||
|
ui->actionLoad_FITS_keywordsLeft->setChecked(settings.value("filemanager/leftLoadFitsKeywords", true).toBool());
|
||||||
|
ui->actionLoad_FITS_keywordsRight->setChecked(settings.value("filemanager/rightLoadFitsKeywords", true).toBool());
|
||||||
|
restoreGeometry(settings.value("filemanager/geometry").toByteArray());
|
||||||
|
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
connect(ui->actionSelect_columnsLeft, &QAction::triggered, this, &FileManager::selectFITSKeywords);
|
||||||
|
connect(ui->actionSelect_columnsRight, &QAction::triggered, this, &FileManager::selectFITSKeywords);
|
||||||
|
connect(ui->actionCopySelectedFilesPathsLeft, &QAction::triggered, this, &FileManager::copySelectedFilesPaths);
|
||||||
|
connect(ui->actionCopySelectedFilesPathsRight, &QAction::triggered, this, &FileManager::copySelectedFilesPaths);
|
||||||
|
connect(ui->leftPath, &QLineEdit::returnPressed, this, &FileManager::pathEdited);
|
||||||
|
connect(ui->rightPath, &QLineEdit::returnPressed, this, &FileManager::pathEdited);
|
||||||
|
|
||||||
|
QFileInfoList drives = QDir::drives();
|
||||||
|
for(auto &drive : drives)
|
||||||
|
{
|
||||||
|
QString path = drive.absoluteFilePath();
|
||||||
|
ui->menuLeft_Tab->addAction(drive.absoluteFilePath(), [path, this](){ ui->leftTab->setDir(path); });
|
||||||
|
ui->menuRight_Tab->addAction(drive.absoluteFilePath(), [path, this](){ ui->rightTab->setDir(path); });
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->progressBar->hide();
|
||||||
|
ui->cancelButton->hide();
|
||||||
|
|
||||||
|
_thread = new QThread(this);
|
||||||
|
_thread->start();
|
||||||
|
_fileTransfer = new FileTransfer(this);
|
||||||
|
_fileTransfer->moveToThread(_thread);
|
||||||
|
connect(_fileTransfer, &FileTransfer::progress, ui->progressBar, &QProgressBar::setValue);
|
||||||
|
connect(_fileTransfer, &FileTransfer::error, this, &FileManager::errorMessage);
|
||||||
|
connect(_fileTransfer, &FileTransfer::finished, [this](){
|
||||||
|
ui->leftTab->setDragEnabled(true);
|
||||||
|
ui->rightTab->setDragEnabled(true);
|
||||||
|
ui->progressBar->hide();
|
||||||
|
ui->cancelButton->hide();
|
||||||
|
});
|
||||||
|
connect(this, &FileManager::copy, _fileTransfer, &FileTransfer::copy);
|
||||||
|
connect(this, &FileManager::move, _fileTransfer, &FileTransfer::move);
|
||||||
|
connect(ui->cancelButton, &QPushButton::clicked, [this](){ _fileTransfer->cancel(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
FileManager::~FileManager()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue("filemanager/leftFitsKeywords", ui->leftTab->FITSKeywords());
|
||||||
|
settings.setValue("filemanager/leftTabPaths", _leftTabBar->tabPaths());
|
||||||
|
settings.setValue("filemanager/leftTabHeader", ui->leftTab->header()->saveState());
|
||||||
|
settings.setValue("filemanager/rightFitsKeywords", ui->rightTab->FITSKeywords());
|
||||||
|
settings.setValue("filemanager/rightTabPaths", _rightTabBar->tabPaths());
|
||||||
|
settings.setValue("filemanager/rightTabHeader", ui->rightTab->header()->saveState());
|
||||||
|
settings.setValue("filemanager/leftLoadFitsKeywords", ui->actionLoad_FITS_keywordsLeft->isChecked());
|
||||||
|
settings.setValue("filemanager/rightLoadFitsKeywords", ui->actionLoad_FITS_keywordsRight->isChecked());
|
||||||
|
settings.setValue("filemanager/geometry", saveGeometry());
|
||||||
|
delete ui;
|
||||||
|
|
||||||
|
_fileTransfer->cancel();
|
||||||
|
_thread->quit();
|
||||||
|
_thread->wait();
|
||||||
|
|
||||||
|
delete _fileTransfer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileManager::selectFITSKeywords()
|
||||||
|
{
|
||||||
|
QStringList columns;
|
||||||
|
if(sender() == ui->actionSelect_columnsLeft)
|
||||||
|
columns = ui->leftTab->FITSKeywords();
|
||||||
|
if(sender() == ui->actionSelect_columnsRight)
|
||||||
|
columns = ui->rightTab->FITSKeywords();
|
||||||
|
|
||||||
|
FITSSelection selection(columns, this);
|
||||||
|
int ret = selection.exec();
|
||||||
|
|
||||||
|
if(ret == QDialog::Accepted)
|
||||||
|
{
|
||||||
|
if(sender() == ui->actionSelect_columnsLeft)
|
||||||
|
ui->leftTab->setFITSKeywords(selection.FITSKeywords());
|
||||||
|
|
||||||
|
if(sender() == ui->actionSelect_columnsRight)
|
||||||
|
ui->rightTab->setFITSKeywords(selection.FITSKeywords());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileManager::copySelectedFilesPaths()
|
||||||
|
{
|
||||||
|
if(sender() == ui->actionCopySelectedFilesPathsLeft)
|
||||||
|
ui->leftTab->copySelectedFilesPathsToClipboard();
|
||||||
|
if(sender() == ui->actionCopySelectedFilesPathsRight)
|
||||||
|
ui->rightTab->copySelectedFilesPathsToClipboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileManager::pathEdited()
|
||||||
|
{
|
||||||
|
if(sender() == ui->leftPath)
|
||||||
|
{
|
||||||
|
QDir dir(ui->leftPath->text());
|
||||||
|
if(dir.exists())
|
||||||
|
ui->leftTab->setDir(dir.absolutePath());
|
||||||
|
}
|
||||||
|
if(sender() == ui->rightPath)
|
||||||
|
{
|
||||||
|
QDir dir(ui->rightPath->text());
|
||||||
|
if(dir.exists())
|
||||||
|
ui->rightTab->setDir(dir.absolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileManager::copyMoveFiles(Qt::DropAction action, const QStringList &src, const QString &dst)
|
||||||
|
{
|
||||||
|
ui->leftTab->setDragEnabled(false);
|
||||||
|
ui->rightTab->setDragEnabled(false);
|
||||||
|
ui->progressBar->show();
|
||||||
|
ui->cancelButton->show();
|
||||||
|
|
||||||
|
switch(action)
|
||||||
|
{
|
||||||
|
case Qt::CopyAction:
|
||||||
|
emit copy(src, dst);
|
||||||
|
break;
|
||||||
|
case Qt::MoveAction:
|
||||||
|
emit move(src, dst);
|
||||||
|
break;
|
||||||
|
case Qt::LinkAction:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::StandardButton FileManager::overwrite(const QString &dst)
|
||||||
|
{
|
||||||
|
QMessageBox::StandardButton button = QMessageBox::question(this, tr("Overwrite file?"), tr("Destination file %1 already exists. Overwrite?").arg(dst),
|
||||||
|
QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel);
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileManager::errorMessage(const QString &title, const QString &text)
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, title, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
QCache<QString, ImageInfoData>* DirFileSystemModel::getCacheInstance()
|
||||||
|
{
|
||||||
|
static bool init = true;
|
||||||
|
static QCache<QString, ImageInfoData> cache;
|
||||||
|
if(!init)
|
||||||
|
{
|
||||||
|
cache.setMaxCost(10000);
|
||||||
|
init = false;
|
||||||
|
}
|
||||||
|
return &cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirFileSystemModel::DirFileSystemModel(QWidget *parentWidget) : QFileSystemModel(parentWidget)
|
||||||
|
,_parentWidget(parentWidget)
|
||||||
|
{
|
||||||
|
_cache = getCacheInstance();
|
||||||
|
setFilter(QDir::AllEntries | QDir::NoDot);
|
||||||
|
_fitsKeywords = {"OBJECT"};
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirFileSystemModel::setDir(const QString &path)
|
||||||
|
{
|
||||||
|
_dir = index(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DirFileSystemModel::dir() const
|
||||||
|
{
|
||||||
|
return fileInfo(_dir).canonicalFilePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirFileSystemModel::setFITSKeywords(const QStringList &keywords)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
_fitsKeywords = keywords;
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList &DirFileSystemModel::FITSKeywords() const
|
||||||
|
{
|
||||||
|
return _fitsKeywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags DirFileSystemModel::flags(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
Qt::ItemFlags ret = QFileSystemModel::flags(index) & ~Qt::ItemIsEditable;
|
||||||
|
if(index.row() == 0)ret &= ~Qt::ItemIsDragEnabled;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DirFileSystemModel::columnCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
return QFileSystemModel::columnCount(parent) + _fitsKeywords.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant DirFileSystemModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if(index.column() >= QFileSystemModel::columnCount() && role == Qt::DisplayRole)
|
||||||
|
{
|
||||||
|
QFileInfo info = fileInfo(index);
|
||||||
|
QString path = info.canonicalFilePath();
|
||||||
|
QString suffix = info.suffix();
|
||||||
|
ImageInfoData *infoData = nullptr;
|
||||||
|
if(_cache->contains(path))
|
||||||
|
{
|
||||||
|
infoData = _cache->object(path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(_loadFitsKeywords)
|
||||||
|
{
|
||||||
|
infoData = new ImageInfoData;
|
||||||
|
if(isFITS(suffix))
|
||||||
|
readFITSHeader(path, *infoData);
|
||||||
|
else if(isXISF(suffix))
|
||||||
|
readXISFHeader(path, *infoData);
|
||||||
|
_cache->insert(path, infoData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(infoData)
|
||||||
|
{
|
||||||
|
int column = index.column() - QFileSystemModel::columnCount();
|
||||||
|
if(column < _fitsKeywords.size())
|
||||||
|
{
|
||||||
|
const QString &key = _fitsKeywords.at(column);
|
||||||
|
for(auto &record : infoData->fitsHeader)
|
||||||
|
if(record.key == key)
|
||||||
|
return record.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return QFileSystemModel::data(index, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant DirFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= QFileSystemModel::columnCount())
|
||||||
|
return _fitsKeywords.at(section - QFileSystemModel::columnCount());
|
||||||
|
|
||||||
|
return QFileSystemModel::headerData(section, orientation, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DirFileSystemModel::hasChildren(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if(parent.parent() == _dir)return false;
|
||||||
|
|
||||||
|
return QFileSystemModel::hasChildren(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DirFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
||||||
|
{
|
||||||
|
Q_UNUSED(row);
|
||||||
|
Q_UNUSED(column);
|
||||||
|
if(data->hasUrls())
|
||||||
|
{
|
||||||
|
QStringList srcPaths;
|
||||||
|
for(auto &url : data->urls())
|
||||||
|
srcPaths.append(url.toLocalFile());
|
||||||
|
emit filesAction(action, srcPaths, filePath(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirFileSystemModel::loadFitsKeywords(bool enable)
|
||||||
|
{
|
||||||
|
_loadFitsKeywords = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirView::DirView(QWidget *parent) : QTreeView(parent)
|
||||||
|
{
|
||||||
|
_dirFileSystemModel = new DirFileSystemModel(this);
|
||||||
|
_dirFileSystemModel->setRootPath(QDir::drives().first().path());
|
||||||
|
_dirFileSystemModel->setReadOnly(false);
|
||||||
|
setDragEnabled(true);
|
||||||
|
setAcceptDrops(true);
|
||||||
|
|
||||||
|
connect(_dirFileSystemModel, &DirFileSystemModel::filesAction, this, &DirView::filesAction);
|
||||||
|
|
||||||
|
setModel(_dirFileSystemModel);
|
||||||
|
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
|
||||||
|
connect(this, &QTreeView::doubleClicked, [this](const QModelIndex &index){
|
||||||
|
QFileInfo info = _dirFileSystemModel->fileInfo(index);
|
||||||
|
if(_dirFileSystemModel->isDir(index))
|
||||||
|
{
|
||||||
|
setDir(info.canonicalFilePath());
|
||||||
|
}
|
||||||
|
else if(info.isFile())
|
||||||
|
{
|
||||||
|
if(_openFilter.contains(info.suffix().toLower()))
|
||||||
|
emit openFile(info.absoluteFilePath());
|
||||||
|
else
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(info.absoluteFilePath()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
connect(header(), &QHeaderView::customContextMenuRequested, this, &DirView::headerContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirView::setDir(const QString &path)
|
||||||
|
{
|
||||||
|
QString oldPath = _dirFileSystemModel->dir();
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
const int ROOT_LEN = 3;
|
||||||
|
#else
|
||||||
|
const int ROOT_LEN = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(oldPath.left(ROOT_LEN) != path.left(ROOT_LEN))
|
||||||
|
_dirFileSystemModel->setRootPath(path.left(ROOT_LEN));
|
||||||
|
|
||||||
|
QString newPath = path;
|
||||||
|
if(!QFileInfo::exists(path))
|
||||||
|
{
|
||||||
|
QDir dir(path);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
dir.setPath(QDir::cleanPath(dir.filePath("..")));
|
||||||
|
}while(!dir.exists() && !dir.isRoot());
|
||||||
|
newPath = dir.path();
|
||||||
|
}
|
||||||
|
|
||||||
|
_dirFileSystemModel->setDir(newPath);
|
||||||
|
setRootIndex(_dirFileSystemModel->index(newPath, 0));
|
||||||
|
clearSelection();
|
||||||
|
if(oldPath != newPath)emit dirChanged(newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DirView::dir() const
|
||||||
|
{
|
||||||
|
return _dirFileSystemModel->dir();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirView::setOpenFilter(const QSet<QString> &openFilter)
|
||||||
|
{
|
||||||
|
_openFilter = openFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirView::setFITSKeywords(const QStringList &keywords)
|
||||||
|
{
|
||||||
|
QString d = dir();
|
||||||
|
_dirFileSystemModel->setFITSKeywords(keywords);
|
||||||
|
setDir(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList &DirView::FITSKeywords() const
|
||||||
|
{
|
||||||
|
return _dirFileSystemModel->FITSKeywords();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirView::headerContextMenu(const QPoint &pos)
|
||||||
|
{
|
||||||
|
QHeaderView *head = header();
|
||||||
|
QMenu menu;
|
||||||
|
int count = head->count();
|
||||||
|
for(int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
QAction *a = menu.addAction(head->model()->headerData(i, Qt::Horizontal).toString());
|
||||||
|
a->setCheckable(true);
|
||||||
|
a->setChecked(!head->isSectionHidden(i));
|
||||||
|
a->setData(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction *a = menu.exec(mapToGlobal(pos));
|
||||||
|
if(a)
|
||||||
|
{
|
||||||
|
if(a->isChecked())head->showSection(a->data().toInt());
|
||||||
|
else head->hideSection(a->data().toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirView::loadFitsKeywords(bool enable)
|
||||||
|
{
|
||||||
|
_dirFileSystemModel->loadFitsKeywords(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirView::copySelectedFilesPathsToClipboard() const
|
||||||
|
{
|
||||||
|
QList<QUrl> urls;
|
||||||
|
QString text;
|
||||||
|
auto selected = selectionModel()->selectedRows();
|
||||||
|
for(auto &item : selected)
|
||||||
|
{
|
||||||
|
if(item.column() == 0)
|
||||||
|
{
|
||||||
|
QString path = _dirFileSystemModel->filePath(item);
|
||||||
|
text.append(path); text.append('\n');
|
||||||
|
urls.append(QUrl::fromLocalFile(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QClipboard *clipboard = QApplication::clipboard();
|
||||||
|
QMimeData *mimeData = new QMimeData();
|
||||||
|
mimeData->setUrls(urls);
|
||||||
|
mimeData->setText(text);
|
||||||
|
clipboard->setMimeData(mimeData);
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
#ifndef FILEMANAGER_H
|
||||||
|
#define FILEMANAGER_H
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QCache>
|
||||||
|
#include <QFileSystemModel>
|
||||||
|
#include <QTreeView>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QTabBar>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include "imageinfodata.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class FileManager;
|
||||||
|
class FITSKeyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileManager;
|
||||||
|
|
||||||
|
class FileTransfer: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit FileTransfer(FileManager *fm);
|
||||||
|
~FileTransfer();
|
||||||
|
public slots:
|
||||||
|
void copy(const QStringList &src, const QString &dst);
|
||||||
|
void move(const QStringList &src, const QString &dst);
|
||||||
|
void cancel();
|
||||||
|
signals:
|
||||||
|
void progress(int percent);
|
||||||
|
void finished();
|
||||||
|
void error(const QString &title, const QString &text);
|
||||||
|
private:
|
||||||
|
void perform(const QStringList &src, const QString &dst, bool copy);
|
||||||
|
struct Action
|
||||||
|
{
|
||||||
|
QString src;
|
||||||
|
QString dst;
|
||||||
|
bool dir = false;
|
||||||
|
};
|
||||||
|
FileManager *_fm;
|
||||||
|
bool _run = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PathTabBar : public QTabBar
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit PathTabBar(const QStringList &tabs);
|
||||||
|
QHBoxLayout* createLayout();
|
||||||
|
const QStringList& tabPaths() const;
|
||||||
|
QString currentTabPath() const;
|
||||||
|
public slots:
|
||||||
|
void pathChanged(const QString &path);
|
||||||
|
signals:
|
||||||
|
void tabChanged(const QString &path);
|
||||||
|
private:
|
||||||
|
QStringList _tabs;
|
||||||
|
QString tabName(const QString &path);
|
||||||
|
};
|
||||||
|
|
||||||
|
class FITSSelection : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
FITSSelection(const QStringList &keywords, QWidget *parent = nullptr);
|
||||||
|
~FITSSelection();
|
||||||
|
QStringList FITSKeywords() const;
|
||||||
|
private:
|
||||||
|
Ui::FITSKeyword *ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileManager : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit FileManager(const QSet<QString> &openFilter, QWidget *parent = nullptr);
|
||||||
|
~FileManager();
|
||||||
|
public slots:
|
||||||
|
void selectFITSKeywords();
|
||||||
|
void copySelectedFilesPaths();
|
||||||
|
void pathEdited();
|
||||||
|
void copyMoveFiles(Qt::DropAction action, const QStringList &src, const QString &dst);
|
||||||
|
QMessageBox::StandardButton overwrite(const QString &dst);
|
||||||
|
void errorMessage(const QString &title, const QString &text);
|
||||||
|
signals:
|
||||||
|
void openFile(const QString &path);
|
||||||
|
void copy(const QStringList &src, const QString &dst);
|
||||||
|
void move(const QStringList &src, const QString &dst);
|
||||||
|
private:
|
||||||
|
Ui::FileManager *ui;
|
||||||
|
PathTabBar *_leftTabBar;
|
||||||
|
PathTabBar *_rightTabBar;
|
||||||
|
QThread *_thread;
|
||||||
|
FileTransfer *_fileTransfer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DirFileSystemModel : public QFileSystemModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit DirFileSystemModel(QWidget *parentWidget);
|
||||||
|
void setDir(const QString &path);
|
||||||
|
QString dir() const;
|
||||||
|
void setFITSKeywords(const QStringList &keywords);
|
||||||
|
const QStringList& FITSKeywords() const;
|
||||||
|
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||||
|
int columnCount(const QModelIndex &parent) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||||
|
bool hasChildren(const QModelIndex &parent) const override;
|
||||||
|
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
|
||||||
|
public slots:
|
||||||
|
void loadFitsKeywords(bool enable);
|
||||||
|
signals:
|
||||||
|
void filesAction(Qt::DropAction action, const QStringList &src, const QString &dst);
|
||||||
|
private:
|
||||||
|
mutable QCache<QString, ImageInfoData> *_cache = nullptr;
|
||||||
|
static QCache<QString, ImageInfoData>* getCacheInstance();
|
||||||
|
QModelIndex _dir;
|
||||||
|
QStringList _fitsKeywords;
|
||||||
|
bool _loadFitsKeywords = true;
|
||||||
|
QWidget *_parentWidget = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DirView : public QTreeView
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
DirFileSystemModel *_dirFileSystemModel = nullptr;
|
||||||
|
QSet<QString> _openFilter;
|
||||||
|
public:
|
||||||
|
explicit DirView(QWidget *parent = nullptr);
|
||||||
|
void setDir(const QString &path);
|
||||||
|
QString dir() const;
|
||||||
|
void setOpenFilter(const QSet<QString> &openFilter);
|
||||||
|
void setFITSKeywords(const QStringList &keywords);
|
||||||
|
const QStringList& FITSKeywords() const;
|
||||||
|
public slots:
|
||||||
|
void headerContextMenu(const QPoint &pos);
|
||||||
|
void loadFitsKeywords(bool enable);
|
||||||
|
void copySelectedFilesPathsToClipboard() const;
|
||||||
|
signals:
|
||||||
|
void dirChanged(const QString &path);
|
||||||
|
void openFile(const QString &path);
|
||||||
|
void filesAction(Qt::DropAction action, const QStringList &src, const QString &dst);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FILEMANAGER_H
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>FileManager</class>
|
||||||
|
<widget class="QMainWindow" name="FileManager">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>1282</width>
|
||||||
|
<height>858</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>File Manager</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="leftLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="leftPath"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="DirView" name="leftTab"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="rightLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="rightPath"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="DirView" name="rightTab"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="progressLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progressBar">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>1</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="cancelButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Cancel</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>1282</width>
|
||||||
|
<height>23</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<widget class="QMenu" name="menuLeft_Tab">
|
||||||
|
<property name="title">
|
||||||
|
<string>Left Tab</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionLoad_FITS_keywordsLeft"/>
|
||||||
|
<addaction name="actionSelect_columnsLeft"/>
|
||||||
|
<addaction name="actionCopySelectedFilesPathsLeft"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
</widget>
|
||||||
|
<widget class="QMenu" name="menuRight_Tab">
|
||||||
|
<property name="title">
|
||||||
|
<string>Right Tab</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionLoad_FITS_keywordsRight"/>
|
||||||
|
<addaction name="actionSelect_columnsRight"/>
|
||||||
|
<addaction name="actionCopySelectedFilesPathsRight"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
</widget>
|
||||||
|
<addaction name="menuLeft_Tab"/>
|
||||||
|
<addaction name="menuRight_Tab"/>
|
||||||
|
</widget>
|
||||||
|
<action name="actionLoad_FITS_keywordsLeft">
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Load FITS keywords</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionLoad_FITS_keywordsRight">
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Load FITS keywords</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionSelect_columnsLeft">
|
||||||
|
<property name="text">
|
||||||
|
<string>Select columns</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionSelect_columnsRight">
|
||||||
|
<property name="text">
|
||||||
|
<string>Select columns</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionCopySelectedFilesPathsLeft">
|
||||||
|
<property name="text">
|
||||||
|
<string>Copy selected files paths</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionCopySelectedFilesPathsRight">
|
||||||
|
<property name="text">
|
||||||
|
<string>Copy selected files paths</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>DirView</class>
|
||||||
|
<extends>QTreeView</extends>
|
||||||
|
<header>filemanager.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
|
||||||
FilesystemWidget::FilesystemWidget(QAbstractItemModel *model, QWidget *parent) : QWidget(parent)
|
FilesystemWidget::FilesystemWidget(QAbstractItemModel *model, QWidget *parent) : QWidget(parent)
|
||||||
, m_model(model)
|
, m_model(model)
|
||||||
@@ -117,6 +118,7 @@ void Filetree::contextMenuEvent(QContextMenuEvent *event)
|
|||||||
{
|
{
|
||||||
setRootIndex(index);
|
setRootIndex(index);
|
||||||
m_rootDir = m_fileSystemModel->filePath(index);
|
m_rootDir = m_fileSystemModel->filePath(index);
|
||||||
|
m_fileSystemModel->setRootPath(m_rootDir);
|
||||||
}
|
}
|
||||||
else if(a == resetRoot)
|
else if(a == resetRoot)
|
||||||
{
|
{
|
||||||
@@ -127,6 +129,7 @@ void Filetree::contextMenuEvent(QContextMenuEvent *event)
|
|||||||
{
|
{
|
||||||
setRootIndex(rootIndex().parent());
|
setRootIndex(rootIndex().parent());
|
||||||
m_rootDir = m_fileSystemModel->filePath(rootIndex().parent());
|
m_rootDir = m_fileSystemModel->filePath(rootIndex().parent());
|
||||||
|
m_fileSystemModel->setRootPath(m_rootDir);
|
||||||
}
|
}
|
||||||
else if(a == copy)
|
else if(a == copy)
|
||||||
{
|
{
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QFileSystemModel>
|
#include <QFileSystemModel>
|
||||||
|
#include <QIdentityProxyModel>
|
||||||
#include <QListView>
|
#include <QListView>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>FITSKeyword</class>
|
||||||
|
<widget class="QDialog" name="FITSKeyword">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>432</width>
|
||||||
|
<height>443</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>FITS Columns</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="keywordList">
|
||||||
|
<property name="dragEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="dragDropMode">
|
||||||
|
<enum>QAbstractItemView::InternalMove</enum>
|
||||||
|
</property>
|
||||||
|
<property name="defaultDropAction">
|
||||||
|
<enum>Qt::MoveAction</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="keyword"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="addButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="removeButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Remove</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>FITSKeyword</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>215</x>
|
||||||
|
<y>420</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>215</x>
|
||||||
|
<y>221</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>FITSKeyword</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>215</x>
|
||||||
|
<y>420</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>215</x>
|
||||||
|
<y>221</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QStyleOption>
|
||||||
|
|
||||||
Histogram::Histogram(QWidget *parent) : QWidget(parent)
|
Histogram::Histogram(QWidget *parent) : QWidget(parent)
|
||||||
{
|
{
|
||||||
@@ -345,18 +345,22 @@ Download::Download(QNetworkReply *reply, const QString indexPath, QObject *paren
|
|||||||
filename.remove(QRegularExpression("\\.zst$"));
|
filename.remove(QRegularExpression("\\.zst$"));
|
||||||
|
|
||||||
_fw.setFileName(indexPath + "/" + filename);
|
_fw.setFileName(indexPath + "/" + filename);
|
||||||
_fw.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
if(_fw.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||||
if(_fw.isOpen())
|
|
||||||
{
|
{
|
||||||
qDebug() << "open file" << _fw.fileName();
|
qDebug() << "open file" << _fw.fileName();
|
||||||
|
_dstream = ZSTD_createDStream();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "Failed to open file" << _fw.fileName();
|
||||||
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
_dstream = ZSTD_createDStream();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Download::~Download()
|
Download::~Download()
|
||||||
{
|
{
|
||||||
ZSTD_freeDStream(_dstream);
|
if(_dstream)
|
||||||
|
ZSTD_freeDStream(_dstream);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::abort()
|
void Download::abort()
|
||||||
@@ -402,7 +406,7 @@ void Download::finished()
|
|||||||
|
|
||||||
void Download::decompress(QByteArray &data)
|
void Download::decompress(QByteArray &data)
|
||||||
{
|
{
|
||||||
if(data.isEmpty())return;
|
if(data.isEmpty() || _dstream == nullptr)return;
|
||||||
|
|
||||||
_hash.addData(data);
|
_hash.addData(data);
|
||||||
|
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
class Download : public QObject
|
class Download : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QNetworkReply *_reply;
|
QNetworkReply *_reply = nullptr;
|
||||||
ZSTD_DStream *_dstream;
|
ZSTD_DStream *_dstream = nullptr;
|
||||||
QFile _fw;
|
QFile _fw;
|
||||||
QCryptographicHash _hash;
|
QCryptographicHash _hash;
|
||||||
public:
|
public:
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#include "imageinfo.h"
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QHeaderView>
|
||||||
|
|
||||||
|
QMap<QString, QColor> headerHighlight;
|
||||||
|
|
||||||
|
ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
|
||||||
|
{
|
||||||
|
setColumnCount(3);
|
||||||
|
setHeaderLabels({tr("Property"), tr("Value"), tr("Comment")});
|
||||||
|
setIndentation(5);
|
||||||
|
QSettings settings;
|
||||||
|
header()->restoreState(settings.value("imageinfo/headerstate").toByteArray());
|
||||||
|
setSortingEnabled(true);
|
||||||
|
header()->setSortIndicatorClearable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageInfo::~ImageInfo()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue("imageinfo/headerstate", header()->saveState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageInfo::setInfo(const ImageInfoData &info)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
if(info.fitsHeader.size())
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *fitsHeader = new QTreeWidgetItem({tr("FITS Header")});
|
||||||
|
for(const FITSRecord &record : info.fitsHeader)
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *item = new QTreeWidgetItem(fitsHeader, {record.key, record.value.toString().left(1024), record.comment});
|
||||||
|
if(headerHighlight.contains(record.key))
|
||||||
|
{
|
||||||
|
QColor color = headerHighlight[record.key];
|
||||||
|
item->setBackground(0, color);
|
||||||
|
item->setBackground(1, color);
|
||||||
|
item->setBackground(2, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addTopLevelItem(fitsHeader);
|
||||||
|
}
|
||||||
|
if(info.info.size())
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *infoHeader = new QTreeWidgetItem({tr("Image info")});
|
||||||
|
for(auto &item : info.info)
|
||||||
|
{
|
||||||
|
new QTreeWidgetItem(infoHeader, {item.first, item.second});
|
||||||
|
}
|
||||||
|
addTopLevelItem(infoHeader);
|
||||||
|
}
|
||||||
|
expandAll();
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef IMAGEINFO_H
|
||||||
|
#define IMAGEINFO_H
|
||||||
|
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include "imageinfodata.h"
|
||||||
|
|
||||||
|
class ImageInfo : public QTreeWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ImageInfo(QWidget *parent);
|
||||||
|
~ImageInfo() override;
|
||||||
|
public slots:
|
||||||
|
void setInfo(const ImageInfoData &info);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // IMAGEINFO_H
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
#include "imageinfo.h"
|
#include "imageinfodata.h"
|
||||||
#include <QSettings>
|
|
||||||
#include <QTime>
|
#include <QTime>
|
||||||
#include <QHeaderView>
|
#include <QRectF>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <wcslib/wcshdr.h>
|
#include <wcslib/wcshdr.h>
|
||||||
#include <wcslib/wcsfix.h>
|
#include <wcslib/wcsfix.h>
|
||||||
#include <libxisf.h>
|
#include "database.h"
|
||||||
|
#include "libxisf.h"
|
||||||
|
|
||||||
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
|
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ FITSRecord::FITSRecord(const LibXISF::FITSKeyword &record)
|
|||||||
string.chop(1);
|
string.chop(1);
|
||||||
string.remove(0, 1);
|
string.remove(0, 1);
|
||||||
}
|
}
|
||||||
|
string = string.trimmed();
|
||||||
bool isint;
|
bool isint;
|
||||||
bool isdouble;
|
bool isdouble;
|
||||||
double vald = string.toDouble(&isdouble);
|
double vald = string.toDouble(&isdouble);
|
||||||
@@ -51,45 +53,6 @@ FITSRecord::FITSRecord(const LibXISF::Property &property)
|
|||||||
xisf = true;
|
xisf = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
|
|
||||||
{
|
|
||||||
setColumnCount(3);
|
|
||||||
setHeaderLabels({tr("Property"), tr("Value"), tr("Comment")});
|
|
||||||
setIndentation(5);
|
|
||||||
QSettings settings;
|
|
||||||
header()->restoreState(settings.value("imageinfo/headerstate").toByteArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageInfo::~ImageInfo()
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
settings.setValue("imageinfo/headerstate", header()->saveState());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageInfo::setInfo(const ImageInfoData &info)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
if(info.fitsHeader.size())
|
|
||||||
{
|
|
||||||
QTreeWidgetItem *fitsHeader = new QTreeWidgetItem({tr("FITS Header")});
|
|
||||||
for(const FITSRecord &record : info.fitsHeader)
|
|
||||||
{
|
|
||||||
new QTreeWidgetItem(fitsHeader, {record.key, record.value.toString().left(1024), record.comment});
|
|
||||||
}
|
|
||||||
addTopLevelItem(fitsHeader);
|
|
||||||
}
|
|
||||||
if(info.info.size())
|
|
||||||
{
|
|
||||||
QTreeWidgetItem *infoHeader = new QTreeWidgetItem({tr("Image info")});
|
|
||||||
for(auto &item : info.info)
|
|
||||||
{
|
|
||||||
new QTreeWidgetItem(infoHeader, {item.first, item.second});
|
|
||||||
}
|
|
||||||
addTopLevelItem(infoHeader);
|
|
||||||
}
|
|
||||||
expandAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WCSDataT::freeWCS()
|
void WCSDataT::freeWCS()
|
||||||
{
|
{
|
||||||
wcsvfree(&nwcs, &wcs);
|
wcsvfree(&nwcs, &wcs);
|
||||||
@@ -124,6 +87,7 @@ WCSDataT::WCSDataT(int width, int height, const QVector<FITSRecord> &header) :
|
|||||||
for(const FITSRecord &record : header)
|
for(const FITSRecord &record : header)
|
||||||
{
|
{
|
||||||
if(record.key.startsWith("PV"))continue;
|
if(record.key.startsWith("PV"))continue;
|
||||||
|
if(record.xisf)continue;
|
||||||
|
|
||||||
QByteArray rec;
|
QByteArray rec;
|
||||||
rec.append(record.key.leftJustified(8, ' '));
|
rec.append(record.key.leftJustified(8, ' '));
|
||||||
@@ -192,9 +156,9 @@ bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
|
bool WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
|
||||||
{
|
{
|
||||||
if(wcs == nullptr)return;
|
if(wcs == nullptr)return false;
|
||||||
|
|
||||||
minRa = 1000;
|
minRa = 1000;
|
||||||
maxRa = -1000;
|
maxRa = -1000;
|
||||||
@@ -247,6 +211,7 @@ void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, dou
|
|||||||
if(s.contains(scp))
|
if(s.contains(scp))
|
||||||
minDec = -90;
|
minDec = -90;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
double hav(double x)
|
double hav(double x)
|
||||||
@@ -305,6 +270,16 @@ QString SkyPoint::toString() const
|
|||||||
return QString("RA: %1 DEC: %2° %3' %4\"").arg(t.toString("HH'h' mm'm' ss's'")).arg(deg, 2, 'f', 0, '0').arg(min, 2, 'f', 0, '0').arg(sec, 2, 'f', 0, '0');
|
return QString("RA: %1 DEC: %2° %3' %4\"").arg(t.toString("HH'h' mm'm' ss's'")).arg(deg, 2, 'f', 0, '0').arg(min, 2, 'f', 0, '0').arg(sec, 2, 'f', 0, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString SkyPoint::RAString() const
|
||||||
|
{
|
||||||
|
return toHMS(ra / 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SkyPoint::DECString() const
|
||||||
|
{
|
||||||
|
return toDMS(dec);
|
||||||
|
}
|
||||||
|
|
||||||
double SkyPoint::fromHMS(const QString &hms)
|
double SkyPoint::fromHMS(const QString &hms)
|
||||||
{
|
{
|
||||||
double deg = fromDMS(hms);
|
double deg = fromDMS(hms);
|
||||||
@@ -347,11 +322,21 @@ QString SkyPoint::toHMS(double decHour)
|
|||||||
|
|
||||||
QString SkyPoint::toDMS(double deg)
|
QString SkyPoint::toDMS(double deg)
|
||||||
{
|
{
|
||||||
|
int sign = deg < 0.0 ? -1 : 1;
|
||||||
|
deg *= sign;
|
||||||
double d,m,s,md;
|
double d,m,s,md;
|
||||||
md = std::modf(deg, &d) * 60.0;
|
md = std::modf(deg, &d) * 60.0;
|
||||||
s = std::modf(md, &m) * 60.0;
|
s = std::modf(md, &m) * 60.0;
|
||||||
|
|
||||||
return QString("%1˚ %2' %3\"").arg((int)d, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
|
return QString("%1˚ %2' %3\"").arg((int)d * sign, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
|
||||||
|
}
|
||||||
|
|
||||||
|
SkyPoint SkyPoint::operator+(const SkyPoint &p)
|
||||||
|
{
|
||||||
|
SkyPoint ret;
|
||||||
|
ret.ra = ra + p.ra;
|
||||||
|
ret.dec = dec + p.dec;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
SkyPointScale ImageInfoData::getCenterRaDec() const
|
SkyPointScale ImageInfoData::getCenterRaDec() const
|
||||||
@@ -442,3 +427,149 @@ SkyPointScale ImageInfoData::getCenterRaDec() const
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SkyPoint greatCircle(SkyPoint &p, double dist, double azm)
|
||||||
|
{
|
||||||
|
dist = dist * M_PI / 180;
|
||||||
|
azm = azm * M_PI / 180;
|
||||||
|
double dec0 = p.DEC() * M_PI / 180;
|
||||||
|
double ra0 = p.RA() * M_PI / 180;
|
||||||
|
double dec1 = std::asin(std::sin(dec0) * std::cos(dist) + std::cos(dec0) * std::sin(dist) * std::cos(azm));
|
||||||
|
double ra1 = ra0 + std::atan2(std::sin(azm) * std::sin(dist) * std::cos(dec0), std::cos(dist) - std::sin(dec0) * std::sin(dec1));
|
||||||
|
return SkyPoint(ra1 * 180 / M_PI, dec1 * 180 / M_PI);
|
||||||
|
}
|
||||||
|
|
||||||
|
SkyGrid WCSDataT::prepareGrid(uint32_t w, uint32_t h, Database *database)
|
||||||
|
{
|
||||||
|
SkyGrid skyGrid;
|
||||||
|
if(!wcs)return skyGrid;
|
||||||
|
|
||||||
|
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
|
||||||
|
calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
|
||||||
|
|
||||||
|
QPointF a,b;
|
||||||
|
worldToPixel(SkyPoint(crVal1, crVal2), a);
|
||||||
|
worldToPixel(SkyPoint(crVal1 + 0.01, crVal2), b);
|
||||||
|
skyGrid.rot_ang = std::atan2(b.y() - a.y(), b.x() - a.x()) / M_PI * -180.0;
|
||||||
|
|
||||||
|
if(database)
|
||||||
|
{
|
||||||
|
double size = std::max(maxRa - minRa, maxDec - minDec);
|
||||||
|
skyGrid.objects = database->getObjects(minRa - size, maxRa + size, minDec - size, maxDec + size);
|
||||||
|
for(auto &object : skyGrid.objects)
|
||||||
|
{
|
||||||
|
QPointF p;
|
||||||
|
if(worldToPixel(object.skyPoint, p))
|
||||||
|
object.pixel = p;
|
||||||
|
|
||||||
|
QPointF majax;
|
||||||
|
worldToPixel(greatCircle(object.skyPoint, (object.min_ax + object.maj_ax) / 120.0, object.pos_ang), majax);
|
||||||
|
majax -= p;
|
||||||
|
object.maj_ax = std::sqrt(QPointF::dotProduct(majax, majax));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double raStep = 15;
|
||||||
|
double decStep = 15;
|
||||||
|
double raRange = maxRa - minRa;
|
||||||
|
double decRange = maxDec - minDec;
|
||||||
|
const QVector<double> raSteps = {15, 5, 2.5, 1.25, 0.25, 20/240.0, 10/240.0, 5/240.0, 1/240.0};
|
||||||
|
const QVector<double> decSteps = {20, 10, 5, 2, 1, 20/60.0, 10/60.0, 5/60.0, 2/60.0, 1/60.0, 20/3600.0, 10/3600.0, 5/3600.0, 2/3600.0, 1/3600.0};
|
||||||
|
|
||||||
|
for(double ra : raSteps)
|
||||||
|
{
|
||||||
|
if(ra * 5 <= raRange)
|
||||||
|
{
|
||||||
|
raStep = ra;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(double dec : decSteps)
|
||||||
|
{
|
||||||
|
if(dec * 5 <= decRange)
|
||||||
|
{
|
||||||
|
decStep = dec;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minRa -= std::fmod(minRa, raStep);
|
||||||
|
minDec -= std::fmod(minDec, decStep);
|
||||||
|
if(minRa < 0)minRa -= raStep;
|
||||||
|
if(minDec < 0)minDec -= decStep;
|
||||||
|
|
||||||
|
QRectF clip(0, 0, w, h);
|
||||||
|
const double step = 0.2;
|
||||||
|
|
||||||
|
maxRa += raStep;
|
||||||
|
maxDec += decStep;
|
||||||
|
for(double ra = minRa; ra <= maxRa; ra += raStep)
|
||||||
|
{
|
||||||
|
QPointF p;
|
||||||
|
worldToPixel(SkyPoint(ra, minDec), p);
|
||||||
|
skyGrid.grid.moveTo(p);
|
||||||
|
for(double dec = minDec + decStep * step; dec <= maxDec; dec += decStep * step)
|
||||||
|
{
|
||||||
|
worldToPixel(SkyPoint(ra, dec), p);
|
||||||
|
skyGrid.grid.lineTo(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(double dec = minDec; dec <= maxDec; dec += decStep)
|
||||||
|
{
|
||||||
|
QPointF p;
|
||||||
|
worldToPixel(SkyPoint(minRa, dec), p);
|
||||||
|
skyGrid.grid.moveTo(p);
|
||||||
|
for(double ra = minRa + raStep * step; ra <= maxRa; ra += raStep * step)
|
||||||
|
{
|
||||||
|
worldToPixel(SkyPoint(ra, dec), p);
|
||||||
|
skyGrid.grid.lineTo(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SkyPoint sp1, sp2,orig;
|
||||||
|
pixelToWorld(QPointF(-1, -1), orig);
|
||||||
|
sp1 = orig;
|
||||||
|
for(uint32_t x = 0; x < w; x++)
|
||||||
|
{
|
||||||
|
QPointF p(x, 0);
|
||||||
|
if(!pixelToWorld(p, sp2))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(static_cast<int>(sp1.RA() / raStep) != static_cast<int>(sp2.RA() / raStep))
|
||||||
|
skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()});
|
||||||
|
|
||||||
|
if(static_cast<int>(sp1.DEC() / decStep) != static_cast<int>(sp2.DEC() / decStep))
|
||||||
|
skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()});
|
||||||
|
|
||||||
|
sp1 = sp2;
|
||||||
|
}
|
||||||
|
|
||||||
|
sp1 = orig;
|
||||||
|
for(uint32_t y = 0; y < h; y++)
|
||||||
|
{
|
||||||
|
QPointF p(0, y);
|
||||||
|
if(!pixelToWorld(p, sp2))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(static_cast<int>(sp1.RA() / raStep) != static_cast<int>(sp2.RA() / raStep))
|
||||||
|
skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()});
|
||||||
|
|
||||||
|
if(static_cast<int>(sp1.DEC() / decStep) != static_cast<int>(sp2.DEC() / decStep))
|
||||||
|
skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()});
|
||||||
|
|
||||||
|
sp1 = sp2;
|
||||||
|
}
|
||||||
|
|
||||||
|
skyGrid.empty = false;
|
||||||
|
return skyGrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkyGrid::clear()
|
||||||
|
{
|
||||||
|
empty = true;
|
||||||
|
grid.clear();
|
||||||
|
text.clear();
|
||||||
|
objects.clear();
|
||||||
|
}
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
#ifndef IMAGEINFO_H
|
#ifndef IMAGEINFODATA_H
|
||||||
#define IMAGEINFO_H
|
#define IMAGEINFODATA_H
|
||||||
|
|
||||||
#include <QTreeWidget>
|
#include <QString>
|
||||||
|
#include <QPointF>
|
||||||
|
#include <QVector>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QPainterPath>
|
||||||
#include <wcslib/wcs.h>
|
#include <wcslib/wcs.h>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace LibXISF { struct FITSKeyword; struct Property; }
|
namespace LibXISF { struct FITSKeyword; struct Property; }
|
||||||
|
|
||||||
|
class Database;
|
||||||
|
|
||||||
struct FITSRecord
|
struct FITSRecord
|
||||||
{
|
{
|
||||||
QByteArray key;
|
QByteArray key;
|
||||||
@@ -33,10 +39,13 @@ public:
|
|||||||
double RAHour() const { return ra / 15.0; }
|
double RAHour() const { return ra / 15.0; }
|
||||||
double DEC() const { return dec; }
|
double DEC() const { return dec; }
|
||||||
QString toString() const;
|
QString toString() const;
|
||||||
|
QString RAString() const;
|
||||||
|
QString DECString() const;
|
||||||
static double fromHMS(const QString &hms);
|
static double fromHMS(const QString &hms);
|
||||||
static double fromDMS(const QString &dms);
|
static double fromDMS(const QString &dms);
|
||||||
static QString toHMS(double decHour);
|
static QString toHMS(double decHour);
|
||||||
static QString toDMS(double deg);
|
static QString toDMS(double deg);
|
||||||
|
SkyPoint operator+(const SkyPoint &p);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SkyPointScale
|
struct SkyPointScale
|
||||||
@@ -48,6 +57,28 @@ struct SkyPointScale
|
|||||||
double scaleHigh = 10000.0;
|
double scaleHigh = 10000.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SkyObject
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QString name2;
|
||||||
|
SkyPoint skyPoint;
|
||||||
|
double maj_ax;
|
||||||
|
double min_ax;
|
||||||
|
double pos_ang;
|
||||||
|
double mag;
|
||||||
|
QPointF pixel;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SkyGrid
|
||||||
|
{
|
||||||
|
bool empty = true;
|
||||||
|
QPainterPath grid;
|
||||||
|
QVector<QPair<QPointF, QString>> text;
|
||||||
|
QVector<SkyObject> objects;
|
||||||
|
double rot_ang = 0;
|
||||||
|
void clear();
|
||||||
|
};
|
||||||
|
|
||||||
class WCSDataT
|
class WCSDataT
|
||||||
{
|
{
|
||||||
int nwcs = 0;
|
int nwcs = 0;
|
||||||
@@ -62,9 +93,10 @@ public:
|
|||||||
~WCSDataT();
|
~WCSDataT();
|
||||||
bool pixelToWorld(const QPointF &pixel, SkyPoint &point) const;
|
bool pixelToWorld(const QPointF &pixel, SkyPoint &point) const;
|
||||||
bool worldToPixel(const SkyPoint &point, QPointF &pixel) const;
|
bool worldToPixel(const SkyPoint &point, QPointF &pixel) const;
|
||||||
void calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
|
bool calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
|
||||||
bool valid() const { return wcs; };
|
bool valid() const { return wcs; };
|
||||||
SkyPointScale getRaDecScale() const;
|
SkyPointScale getRaDecScale() const;
|
||||||
|
SkyGrid prepareGrid(uint32_t w, uint32_t h, Database *database);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ImageInfoData
|
struct ImageInfoData
|
||||||
@@ -73,10 +105,10 @@ struct ImageInfoData
|
|||||||
QVector<QPair<QString, QString>> info;
|
QVector<QPair<QString, QString>> info;
|
||||||
std::shared_ptr<WCSDataT> wcs;
|
std::shared_ptr<WCSDataT> wcs;
|
||||||
SkyPointScale getCenterRaDec() const;
|
SkyPointScale getCenterRaDec() const;
|
||||||
|
int index = 0;
|
||||||
|
int num = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(ImageInfoData);
|
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
@@ -85,14 +117,6 @@ typedef enum
|
|||||||
Stars,
|
Stars,
|
||||||
}AnalyzeLevel;
|
}AnalyzeLevel;
|
||||||
|
|
||||||
class ImageInfo : public QTreeWidget
|
Q_DECLARE_METATYPE(ImageInfoData);
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ImageInfo(QWidget *parent);
|
|
||||||
~ImageInfo() override;
|
|
||||||
public slots:
|
|
||||||
void setInfo(const ImageInfoData &info);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // IMAGEINFO_H
|
#endif // IMAGEINFODATA_H
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include "loadrunable.h"
|
#include "loadrunable.h"
|
||||||
#include "rawimage.h"
|
#include "rawimage.h"
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
@@ -22,13 +23,16 @@ Image::Image(const QString name, int number, ImageRingList *ringList) :
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Image::load(QThreadPool *pool)
|
void Image::load(int index, QThreadPool *pool)
|
||||||
{
|
{
|
||||||
|
if(index != m_info.index && !m_loading)
|
||||||
|
m_rawImage.reset();
|
||||||
|
|
||||||
if(!m_rawImage && !m_loading)
|
if(!m_rawImage && !m_loading)
|
||||||
{
|
{
|
||||||
m_loading = true;
|
m_loading = true;
|
||||||
m_released = false;
|
m_released = false;
|
||||||
pool->start(new LoadRunable(m_name, this, m_ringList->analyzeLevel()));
|
pool->start(new LoadRunable(m_name, this, m_ringList->analyzeLevel(), index));
|
||||||
}
|
}
|
||||||
if(!m_loading && m_rawImage)
|
if(!m_loading && m_rawImage)
|
||||||
emit pixmapLoaded(this);
|
emit pixmapLoaded(this);
|
||||||
@@ -37,7 +41,7 @@ void Image::load(QThreadPool *pool)
|
|||||||
void Image::loadThumbnail(QThreadPool *pool)
|
void Image::loadThumbnail(QThreadPool *pool)
|
||||||
{
|
{
|
||||||
if(!m_thumbnail)
|
if(!m_thumbnail)
|
||||||
pool->start(new LoadRunable(m_name, this, AnalyzeLevel::None, true));
|
pool->start(new LoadRunable(m_name, this, AnalyzeLevel::None, 0, true));
|
||||||
else
|
else
|
||||||
emit thumbnailLoaded(this);
|
emit thumbnailLoaded(this);
|
||||||
}
|
}
|
||||||
@@ -84,6 +88,11 @@ void Image::clearThumbnail()
|
|||||||
m_thumbnail.reset();
|
m_thumbnail.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Image::isLoading() const
|
||||||
|
{
|
||||||
|
return m_loading;
|
||||||
|
}
|
||||||
|
|
||||||
void Image::imageLoaded(std::shared_ptr<RawImage> rawImage, ImageInfoData info)
|
void Image::imageLoaded(std::shared_ptr<RawImage> rawImage, ImageInfoData info)
|
||||||
{
|
{
|
||||||
m_loading = false;
|
m_loading = false;
|
||||||
@@ -102,13 +111,14 @@ void Image::thumbnailLoadFinish(std::shared_ptr<RawImage> rawImage)
|
|||||||
emit thumbnailLoaded(this);
|
emit thumbnailLoaded(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter, QObject *parent) : QAbstractItemModel(parent)
|
ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter, QObject *parent) : QAbstractListModel(parent)
|
||||||
, m_liveMode(false)
|
, m_liveMode(false)
|
||||||
, m_analyzeLevel(None)
|
, m_analyzeLevel(None)
|
||||||
, m_database(database)
|
, m_database(database)
|
||||||
, m_nameFilter(nameFilter)
|
, m_nameFilter(nameFilter)
|
||||||
|
, m_fileSuffix(nameFilter)
|
||||||
{
|
{
|
||||||
connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
|
connect(&m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &ImageRingList::dirChanged);
|
||||||
m_nameFilter.replaceInStrings(QRegularExpression("^"), "*.");
|
m_nameFilter.replaceInStrings(QRegularExpression("^"), "*.");
|
||||||
m_loadPool = new QThreadPool(this);
|
m_loadPool = new QThreadPool(this);
|
||||||
m_loadPool->setThreadPriority(QThread::LowPriority);
|
m_loadPool->setThreadPriority(QThread::LowPriority);
|
||||||
@@ -139,6 +149,7 @@ bool ImageRingList::setDir(const QString path, const QString ¤tFile, bool
|
|||||||
|
|
||||||
if(dir.exists())
|
if(dir.exists())
|
||||||
{
|
{
|
||||||
|
m_currentDir = path;
|
||||||
QStringList scannedDirs;
|
QStringList scannedDirs;
|
||||||
QStringList absolutePaths;
|
QStringList absolutePaths;
|
||||||
std::function<void(const QString&)> scanDir = [&](const QString &path)
|
std::function<void(const QString&)> scanDir = [&](const QString &path)
|
||||||
@@ -164,10 +175,11 @@ bool ImageRingList::setDir(const QString path, const QString ¤tFile, bool
|
|||||||
};
|
};
|
||||||
|
|
||||||
scanDir(path);
|
scanDir(path);
|
||||||
qDebug() << absolutePaths.size();
|
//qDebug() << absolutePaths.size();
|
||||||
setFiles(absolutePaths, m_liveMode ? absolutePaths.first() : currentFile);
|
setFilesPrivate(absolutePaths, m_liveMode ? absolutePaths.first() : currentFile);
|
||||||
|
|
||||||
m_fileSystemWatcher.removePaths(m_fileSystemWatcher.directories());
|
if(m_fileSystemWatcher.directories().size())
|
||||||
|
m_fileSystemWatcher.removePaths(m_fileSystemWatcher.directories());
|
||||||
m_fileSystemWatcher.addPath(path);
|
m_fileSystemWatcher.addPath(path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -186,6 +198,17 @@ void ImageRingList::setFile(const QString &file)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageRingList::setFiles(QStringList files)
|
||||||
|
{
|
||||||
|
QRegularExpression reg("(" + m_fileSuffix.join("|") + ")");
|
||||||
|
files.removeIf([®](const QString &file){
|
||||||
|
QFileInfo info(file);
|
||||||
|
auto match = reg.match(info.suffix());
|
||||||
|
return !match.hasMatch() || !info.exists() || !info.isReadable() || !info.isFile();
|
||||||
|
});
|
||||||
|
setFilesPrivate(files);
|
||||||
|
}
|
||||||
|
|
||||||
ImagePtr ImageRingList::currentImage()
|
ImagePtr ImageRingList::currentImage()
|
||||||
{
|
{
|
||||||
if(m_images.size())
|
if(m_images.size())
|
||||||
@@ -194,20 +217,25 @@ ImagePtr ImageRingList::currentImage()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ImageRingList::currentDir() const
|
||||||
|
{
|
||||||
|
return m_currentDir;
|
||||||
|
}
|
||||||
|
|
||||||
void ImageRingList::increment()
|
void ImageRingList::increment()
|
||||||
{
|
{
|
||||||
if(m_images.size())
|
if(m_images.size())
|
||||||
{
|
{
|
||||||
//don't increment if current image was not loaded yet
|
//don't increment if current image was not loaded yet
|
||||||
if(!(*m_currImage)->rawImage())
|
if((*m_currImage)->isLoading())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
(*m_firstImage)->release();
|
(*m_firstImage)->release();
|
||||||
m_firstImage = increment(m_firstImage);
|
m_firstImage = increment(m_firstImage);
|
||||||
m_currImage = increment(m_currImage);
|
m_currImage = increment(m_currImage);
|
||||||
(*m_currImage)->load(m_loadPool);
|
(*m_currImage)->load(0, m_loadPool);
|
||||||
m_lastImage = increment(m_lastImage);
|
m_lastImage = increment(m_lastImage);
|
||||||
(*m_lastImage)->load(m_loadPool);
|
(*m_lastImage)->load(0, m_loadPool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,23 +243,65 @@ void ImageRingList::decrement()
|
|||||||
{
|
{
|
||||||
if(m_images.size())
|
if(m_images.size())
|
||||||
{
|
{
|
||||||
|
//don't decrement if current image was not loaded yet
|
||||||
|
if((*m_currImage)->isLoading())
|
||||||
|
return;
|
||||||
|
|
||||||
(*m_lastImage)->release();
|
(*m_lastImage)->release();
|
||||||
m_firstImage = decrement(m_firstImage);
|
m_firstImage = decrement(m_firstImage);
|
||||||
m_currImage = decrement(m_currImage);
|
m_currImage = decrement(m_currImage);
|
||||||
(*m_currImage)->load(m_loadPool);
|
(*m_currImage)->load(0, m_loadPool);
|
||||||
m_lastImage = decrement(m_lastImage);
|
m_lastImage = decrement(m_lastImage);
|
||||||
(*m_firstImage)->load(m_loadPool);
|
(*m_firstImage)->load(0, m_loadPool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageRingList::prevSubImage()
|
||||||
|
{
|
||||||
|
if(m_images.size())
|
||||||
|
{
|
||||||
|
if((*m_currImage)->isLoading())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int index = (*m_currImage)->info().index;
|
||||||
|
int num = (*m_currImage)->info().num;
|
||||||
|
if(num > 1)
|
||||||
|
(*m_currImage)->load(index == 0 ? num - 1 : index - 1, m_loadPool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageRingList::nextSubImage()
|
||||||
|
{
|
||||||
|
if(m_images.size())
|
||||||
|
{
|
||||||
|
if((*m_currImage)->isLoading())
|
||||||
|
return;
|
||||||
|
|
||||||
|
int index = (*m_currImage)->info().index;
|
||||||
|
int num = (*m_currImage)->info().num;
|
||||||
|
if(num > 1)
|
||||||
|
(*m_currImage)->load((index + 1) % num, m_loadPool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageRingList::setMarked()
|
void ImageRingList::setMarked()
|
||||||
{
|
{
|
||||||
QStringList files = m_database->getMarkedFiles();
|
QStringList files = m_database->getMarkedFiles();
|
||||||
std::remove_if(files.begin(), files.end(), [](const QString &file){
|
files.removeIf([](const QString &file){
|
||||||
QFileInfo info(file);
|
QFileInfo info(file);
|
||||||
return !info.exists() || !info.isReadable();
|
return !info.exists() || !info.isReadable();
|
||||||
});
|
});
|
||||||
setFiles(files);
|
setFilesPrivate(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageRingList::reloadImage()
|
||||||
|
{
|
||||||
|
if(*m_currImage)
|
||||||
|
{
|
||||||
|
int index = (*m_currImage)->info().index;
|
||||||
|
(*m_currImage)->release();
|
||||||
|
(*m_currImage)->load(index, m_loadPool);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageRingList::setLiveMode(bool live)
|
void ImageRingList::setLiveMode(bool live)
|
||||||
@@ -274,7 +344,7 @@ void ImageRingList::loadFile(int row)
|
|||||||
if(m_images.empty())
|
if(m_images.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
(*m_currImage)->load(m_loadPool);
|
(*m_currImage)->load(0, m_loadPool);
|
||||||
|
|
||||||
m_width = DEFAULT_WIDTH<m_images.size()/2 ? DEFAULT_WIDTH : m_images.size()/2;
|
m_width = DEFAULT_WIDTH<m_images.size()/2 ? DEFAULT_WIDTH : m_images.size()/2;
|
||||||
if(m_liveMode)
|
if(m_liveMode)
|
||||||
@@ -283,9 +353,9 @@ void ImageRingList::loadFile(int row)
|
|||||||
for(int i=0; i<m_width; i++)
|
for(int i=0; i<m_width; i++)
|
||||||
{
|
{
|
||||||
m_firstImage = decrement(m_firstImage);
|
m_firstImage = decrement(m_firstImage);
|
||||||
(*m_firstImage)->load(m_loadPool);
|
(*m_firstImage)->load(0, m_loadPool);
|
||||||
m_lastImage = increment(m_lastImage);
|
m_lastImage = increment(m_lastImage);
|
||||||
(*m_lastImage)->load(m_loadPool);
|
(*m_lastImage)->load(0, m_loadPool);
|
||||||
}
|
}
|
||||||
if(m_lastImage != m_firstImage)
|
if(m_lastImage != m_firstImage)
|
||||||
{
|
{
|
||||||
@@ -342,7 +412,7 @@ void ImageRingList::clearThumbnails()
|
|||||||
img->clearThumbnail();
|
img->clearThumbnail();
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex ImageRingList::index(int row, int column, const QModelIndex &parent) const
|
/*QModelIndex ImageRingList::index(int row, int column, const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
return createIndex(row, column, m_images.at(row).get());
|
return createIndex(row, column, m_images.at(row).get());
|
||||||
@@ -352,7 +422,7 @@ QModelIndex ImageRingList::parent(const QModelIndex &child) const
|
|||||||
{
|
{
|
||||||
Q_UNUSED(child);
|
Q_UNUSED(child);
|
||||||
return QModelIndex();
|
return QModelIndex();
|
||||||
}
|
}*/
|
||||||
|
|
||||||
int ImageRingList::rowCount(const QModelIndex &parent) const
|
int ImageRingList::rowCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
@@ -362,31 +432,35 @@ int ImageRingList::rowCount(const QModelIndex &parent) const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ImageRingList::columnCount(const QModelIndex &parent) const
|
/*int ImageRingList::columnCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
QVariant ImageRingList::data(const QModelIndex &index, int role) const
|
QVariant ImageRingList::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
switch(role)
|
if(index.isValid() && index.row() >= 0 && index.row() < m_images.size())
|
||||||
{
|
{
|
||||||
case Qt::DisplayRole:
|
switch(role)
|
||||||
{
|
{
|
||||||
QFileInfo info(m_images.at(index.row())->name());
|
case Qt::DisplayRole:
|
||||||
return info.fileName();
|
{
|
||||||
}
|
QFileInfo info(m_images.at(index.row())->name());
|
||||||
case Qt::FontRole:
|
return info.fileName();
|
||||||
{
|
}
|
||||||
bool marked = m_database->isMarked(m_images.at(index.row())->name());
|
case Qt::FontRole:
|
||||||
QFont font;
|
{
|
||||||
font.setBold(marked);
|
bool marked = m_database->isMarked(m_images.at(index.row())->name());
|
||||||
return font;
|
QFont font;
|
||||||
}
|
font.setBold(marked);
|
||||||
default:
|
return font;
|
||||||
return QVariant();
|
}
|
||||||
|
default:
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ImageRingList::headerData(int section, Qt::Orientation orientation, int role) const
|
QVariant ImageRingList::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
@@ -409,9 +483,9 @@ void ImageRingList::setPreload(int width)
|
|||||||
for(int i = newWidth - m_width; i>0; i--)
|
for(int i = newWidth - m_width; i>0; i--)
|
||||||
{
|
{
|
||||||
m_firstImage = decrement(m_firstImage);
|
m_firstImage = decrement(m_firstImage);
|
||||||
(*m_firstImage)->load(m_loadPool);
|
(*m_firstImage)->load(0, m_loadPool);
|
||||||
m_lastImage = increment(m_lastImage);
|
m_lastImage = increment(m_lastImage);
|
||||||
(*m_lastImage)->load(m_loadPool);
|
(*m_lastImage)->load(0, m_loadPool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(newWidth < m_width)
|
if(newWidth < m_width)
|
||||||
@@ -464,7 +538,7 @@ void ImageRingList::toggleSlideshow(bool start)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageRingList::setFiles(const QStringList files, const QString ¤tFile)
|
void ImageRingList::setFilesPrivate(const QStringList files, const QString ¤tFile)
|
||||||
{
|
{
|
||||||
m_loadPool->clear();
|
m_loadPool->clear();
|
||||||
m_thumbPool->clear();
|
m_thumbPool->clear();
|
||||||
@@ -476,8 +550,8 @@ void ImageRingList::setFiles(const QStringList files, const QString ¤tFile
|
|||||||
for(const QString &file : files)
|
for(const QString &file : files)
|
||||||
{
|
{
|
||||||
ImagePtr ptr = make_shared<Image>(file, i++, this);
|
ImagePtr ptr = make_shared<Image>(file, i++, this);
|
||||||
connect(ptr.get(), SIGNAL(pixmapLoaded(Image*)), this, SLOT(imageLoaded(Image*)));
|
connect(ptr.get(), &Image::pixmapLoaded, this, &ImageRingList::imageLoaded);
|
||||||
connect(ptr.get(), SIGNAL(thumbnailLoaded(Image*)), this, SIGNAL(thumbnailLoaded(Image*)));
|
connect(ptr.get(), &Image::thumbnailLoaded, this, &ImageRingList::thumbnailLoaded);
|
||||||
m_images.append(ptr);
|
m_images.append(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,9 +590,8 @@ void ImageRingList::imageLoaded(Image *image)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageRingList::dirChanged(QString dir)
|
void ImageRingList::dirChanged(QString)
|
||||||
{
|
{
|
||||||
m_currentDir = dir;
|
|
||||||
if(m_liveMode)
|
if(m_liveMode)
|
||||||
reloadDir();
|
reloadDir();
|
||||||
else
|
else
|
||||||
@@ -7,8 +7,9 @@
|
|||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "imageinfo.h"
|
#include "imageinfodata.h"
|
||||||
#include "rawimage.h"
|
#include "rawimage.h"
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
|
||||||
class ImageRingList;
|
class ImageRingList;
|
||||||
class QThreadPool;
|
class QThreadPool;
|
||||||
@@ -27,7 +28,7 @@ class Image : public QObject
|
|||||||
ImageRingList *m_ringList;
|
ImageRingList *m_ringList;
|
||||||
public:
|
public:
|
||||||
explicit Image(const QString name, int number, ImageRingList *ringList);
|
explicit Image(const QString name, int number, ImageRingList *ringList);
|
||||||
void load(QThreadPool *pool);
|
void load(int index, QThreadPool *pool);
|
||||||
void loadThumbnail(QThreadPool *pool);
|
void loadThumbnail(QThreadPool *pool);
|
||||||
void release();
|
void release();
|
||||||
QString name() const;
|
QString name() const;
|
||||||
@@ -37,6 +38,7 @@ public:
|
|||||||
bool isCurrent() const;
|
bool isCurrent() const;
|
||||||
int number() const;
|
int number() const;
|
||||||
void clearThumbnail();
|
void clearThumbnail();
|
||||||
|
bool isLoading() const;
|
||||||
signals:
|
signals:
|
||||||
void pixmapLoaded(Image *ptr);
|
void pixmapLoaded(Image *ptr);
|
||||||
void thumbnailLoaded(Image *ptr);
|
void thumbnailLoaded(Image *ptr);
|
||||||
@@ -49,7 +51,7 @@ typedef std::shared_ptr<Image> ImagePtr;
|
|||||||
|
|
||||||
class Database;
|
class Database;
|
||||||
|
|
||||||
class ImageRingList : public QAbstractItemModel
|
class ImageRingList : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
int m_width;
|
int m_width;
|
||||||
@@ -66,6 +68,7 @@ class ImageRingList : public QAbstractItemModel
|
|||||||
QThreadPool *m_thumbPool;
|
QThreadPool *m_thumbPool;
|
||||||
Database *m_database;
|
Database *m_database;
|
||||||
QStringList m_nameFilter;
|
QStringList m_nameFilter;
|
||||||
|
QStringList m_fileSuffix;
|
||||||
QTimer *m_slideShowTimer;
|
QTimer *m_slideShowTimer;
|
||||||
QTimer *m_dirChangeDelay;
|
QTimer *m_dirChangeDelay;
|
||||||
QString m_currentDir;
|
QString m_currentDir;
|
||||||
@@ -74,7 +77,9 @@ public:
|
|||||||
~ImageRingList() override;
|
~ImageRingList() override;
|
||||||
bool setDir(const QString path, const QString ¤tFile = QString(), bool recursive = false);
|
bool setDir(const QString path, const QString ¤tFile = QString(), bool recursive = false);
|
||||||
void setFile(const QString &file);
|
void setFile(const QString &file);
|
||||||
|
void setFiles(QStringList files);
|
||||||
ImagePtr currentImage();
|
ImagePtr currentImage();
|
||||||
|
QString currentDir() const;
|
||||||
void setLiveMode(bool live);
|
void setLiveMode(bool live);
|
||||||
void setCalculateStats(bool stats);
|
void setCalculateStats(bool stats);
|
||||||
void setFindPeaks(bool findPeaks);
|
void setFindPeaks(bool findPeaks);
|
||||||
@@ -88,10 +93,10 @@ public:
|
|||||||
void updateMark();
|
void updateMark();
|
||||||
void clearThumbnails();
|
void clearThumbnails();
|
||||||
|
|
||||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
//QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QModelIndex parent(const QModelIndex &child) const override;
|
//QModelIndex parent(const QModelIndex &child) const override;
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
//int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
public slots:
|
public slots:
|
||||||
@@ -101,9 +106,12 @@ public slots:
|
|||||||
void toggleSlideshow(bool start);
|
void toggleSlideshow(bool start);
|
||||||
void increment();
|
void increment();
|
||||||
void decrement();
|
void decrement();
|
||||||
|
void prevSubImage();
|
||||||
|
void nextSubImage();
|
||||||
void setMarked();
|
void setMarked();
|
||||||
|
void reloadImage();
|
||||||
protected:
|
protected:
|
||||||
void setFiles(const QStringList files, const QString ¤tFile = QString());
|
void setFilesPrivate(const QStringList files, const QString ¤tFile = QString());
|
||||||
QList<ImagePtr>::iterator increment(QList<ImagePtr>::iterator iter);
|
QList<ImagePtr>::iterator increment(QList<ImagePtr>::iterator iter);
|
||||||
QList<ImagePtr>::iterator decrement(QList<ImagePtr>::iterator iter);
|
QList<ImagePtr>::iterator decrement(QList<ImagePtr>::iterator iter);
|
||||||
signals:
|
signals:
|
||||||
@@ -113,7 +121,7 @@ signals:
|
|||||||
void currentImageChanged(int index);
|
void currentImageChanged(int index);
|
||||||
protected slots:
|
protected slots:
|
||||||
void imageLoaded(Image *image);
|
void imageLoaded(Image *image);
|
||||||
void dirChanged(QString dir);
|
void dirChanged(QString);
|
||||||
void reloadDir();
|
void reloadDir();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,8 +27,8 @@ ImageScrollArea::ImageScrollArea(Database *database, QWidget *parent) : QWidget(
|
|||||||
layout->addWidget(m_verticalScrollBar, 0, 1);
|
layout->addWidget(m_verticalScrollBar, 0, 1);
|
||||||
layout->addWidget(m_horizontalScrollBar, 1, 0);
|
layout->addWidget(m_horizontalScrollBar, 1, 0);
|
||||||
|
|
||||||
connect(m_verticalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollEvent()));
|
connect(m_verticalScrollBar, &QScrollBar::valueChanged, this, &ImageScrollArea::scrollEvent);
|
||||||
connect(m_horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollEvent()));
|
connect(m_horizontalScrollBar, &QScrollBar::valueChanged, this, &ImageScrollArea::scrollEvent);
|
||||||
|
|
||||||
if(imageWidgetGL)
|
if(imageWidgetGL)
|
||||||
{
|
{
|
||||||
@@ -58,6 +58,11 @@ void ImageScrollArea::setBayerMask(int mask)
|
|||||||
m_imageWidget->setBayerMask(mask);
|
m_imageWidget->setBayerMask(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageScrollArea::setColormap(int colormap)
|
||||||
|
{
|
||||||
|
m_imageWidget->setColormap(colormap);
|
||||||
|
}
|
||||||
|
|
||||||
void ImageScrollArea::updateScrollbars(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV)
|
void ImageScrollArea::updateScrollbars(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV)
|
||||||
{
|
{
|
||||||
if(maxH > 0)
|
if(maxH > 0)
|
||||||
@@ -105,7 +110,7 @@ void ImageScrollArea::oneToOne()
|
|||||||
|
|
||||||
void ImageScrollArea::imageLoaded(Image *image)
|
void ImageScrollArea::imageLoaded(Image *image)
|
||||||
{
|
{
|
||||||
if(image && image->rawImage())
|
if(image)
|
||||||
{
|
{
|
||||||
m_imageWidget->setImage(image->rawImage(), image->number());
|
m_imageWidget->setImage(image->rawImage(), image->number());
|
||||||
m_imageWidget->setWCS(image->info().wcs);
|
m_imageWidget->setWCS(image->info().wcs);
|
||||||
@@ -137,6 +142,11 @@ void ImageScrollArea::falseColor(bool enable)
|
|||||||
m_imageWidget->falseColor(enable);
|
m_imageWidget->falseColor(enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageScrollArea::drawGrid(bool enable)
|
||||||
|
{
|
||||||
|
m_imageWidget->drawGrid(enable);
|
||||||
|
}
|
||||||
|
|
||||||
QImage ImageScrollArea::renderToImage()
|
QImage ImageScrollArea::renderToImage()
|
||||||
{
|
{
|
||||||
return m_imageWidget->renderToImage();
|
return m_imageWidget->renderToImage();
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#define IMAGESCROLLAREA_H
|
#define IMAGESCROLLAREA_H
|
||||||
|
|
||||||
#include "imagewidget.h"
|
#include "imagewidget.h"
|
||||||
|
#include <QScrollBar>
|
||||||
|
|
||||||
class ImageScrollArea : public QWidget
|
class ImageScrollArea : public QWidget
|
||||||
{
|
{
|
||||||
@@ -16,6 +17,7 @@ public:
|
|||||||
void allocateThumbnails(const QStringList &paths);
|
void allocateThumbnails(const QStringList &paths);
|
||||||
void showThumbnail(bool enable);
|
void showThumbnail(bool enable);
|
||||||
void setBayerMask(int mask);
|
void setBayerMask(int mask);
|
||||||
|
void setColormap(int colormap);
|
||||||
protected:
|
protected:
|
||||||
void updateScrollbars(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV);
|
void updateScrollbars(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV);
|
||||||
public slots:
|
public slots:
|
||||||
@@ -29,6 +31,7 @@ public slots:
|
|||||||
void invert(bool enable);
|
void invert(bool enable);
|
||||||
void superPixel(bool enable);
|
void superPixel(bool enable);
|
||||||
void falseColor(bool enable);
|
void falseColor(bool enable);
|
||||||
|
void drawGrid(bool enable);
|
||||||
QImage renderToImage();
|
QImage renderToImage();
|
||||||
protected slots:
|
protected slots:
|
||||||
void scrollEvent();
|
void scrollEvent();
|
||||||
@@ -9,12 +9,15 @@
|
|||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QDragEnterEvent>
|
#include <QDragEnterEvent>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include "imageringlist.h"
|
#include <QStandardPaths>
|
||||||
#include <QFloat16>
|
#include <QFloat16>
|
||||||
|
#include <QStyle>
|
||||||
|
#include "imageringlist.h"
|
||||||
|
|
||||||
int FILTERING = 1;
|
int FILTERING = 1;
|
||||||
bool OpenGLES = false;
|
bool OpenGLES = false;
|
||||||
const int LUT_SIZE = 32;
|
const int LUT_SIZE = 32;
|
||||||
|
bool BESTFIT = false;
|
||||||
|
|
||||||
struct RawImageType
|
struct RawImageType
|
||||||
{
|
{
|
||||||
@@ -76,7 +79,7 @@ ImageWidgetGL::ImageWidgetGL(Database *database, QWidget *parent) : QOpenGLWidge
|
|||||||
m_updateTimer = new QTimer(this);
|
m_updateTimer = new QTimer(this);
|
||||||
m_updateTimer->setInterval(500);
|
m_updateTimer->setInterval(500);
|
||||||
m_updateTimer->setSingleShot(true);
|
m_updateTimer->setSingleShot(true);
|
||||||
connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(update()));
|
connect(m_updateTimer, &QTimer::timeout, this, static_cast<void (QOpenGLWidget::*)()>(&ImageWidgetGL::update));
|
||||||
setAcceptDrops(true);
|
setAcceptDrops(true);
|
||||||
QTimer::singleShot(1000, [this](){
|
QTimer::singleShot(1000, [this](){
|
||||||
if(!isValid())
|
if(!isValid())
|
||||||
@@ -87,6 +90,7 @@ ImageWidgetGL::ImageWidgetGL(Database *database, QWidget *parent) : QOpenGLWidge
|
|||||||
});
|
});
|
||||||
|
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
|
m_bestFit = BESTFIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageWidgetGL::~ImageWidgetGL()
|
ImageWidgetGL::~ImageWidgetGL()
|
||||||
@@ -150,7 +154,7 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
|
|||||||
m_image->bind();
|
m_image->bind();
|
||||||
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
f->glGenerateMipmap(GL_TEXTURE_2D);
|
f->glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
qDebug() << "setImage" << timer.elapsed();
|
qDebug() << "ImageWidgetGL::setImage" << timer.elapsed() << "ms";
|
||||||
m_swPaint = f->glGetError() != GL_NO_ERROR;
|
m_swPaint = f->glGetError() != GL_NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +180,10 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
|
|||||||
void ImageWidgetGL::setWCS(std::shared_ptr<WCSDataT> wcs)
|
void ImageWidgetGL::setWCS(std::shared_ptr<WCSDataT> wcs)
|
||||||
{
|
{
|
||||||
m_wcs = wcs;
|
m_wcs = wcs;
|
||||||
|
m_grid.clear();
|
||||||
|
|
||||||
|
if(m_drawGrid && m_wcs)
|
||||||
|
m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageWidgetGL::zoom(int zoom, const QPointF &mousePos)
|
void ImageWidgetGL::zoom(int zoom, const QPointF &mousePos)
|
||||||
@@ -258,6 +266,12 @@ void ImageWidgetGL::setBayerMask(int mask)
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageWidgetGL::setColormap(int colormap)
|
||||||
|
{
|
||||||
|
m_colormapIdx = colormap;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
void ImageWidgetGL::setMTFParams(const MTFParam ¶ms)
|
void ImageWidgetGL::setMTFParams(const MTFParam ¶ms)
|
||||||
{
|
{
|
||||||
m_mtfParams = params;
|
m_mtfParams = params;
|
||||||
@@ -359,7 +373,7 @@ void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scal
|
|||||||
auto mtf = [&mtfParams](int i, float x)
|
auto mtf = [&mtfParams](int i, float x)
|
||||||
{
|
{
|
||||||
x = (x - mtfParams.blackPoint[i]) / (mtfParams.whitePoint[i] - mtfParams.blackPoint[i]);
|
x = (x - mtfParams.blackPoint[i]) / (mtfParams.whitePoint[i] - mtfParams.blackPoint[i]);
|
||||||
x = std::min(std::max(x, 0.0f), 1.0f);
|
x = std::clamp(x, 0.0f, 1.0f);
|
||||||
return ((mtfParams.midPoint[i] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[i] - 1.0f) * x - mtfParams.midPoint[i]);
|
return ((mtfParams.midPoint[i] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[i] - 1.0f) * x - mtfParams.midPoint[i]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -382,9 +396,9 @@ void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scal
|
|||||||
float iptr;
|
float iptr;
|
||||||
float fy = std::modf((y + oy) * iscale, &iptr);
|
float fy = std::modf((y + oy) * iscale, &iptr);
|
||||||
int64_t py = iptr;
|
int64_t py = iptr;
|
||||||
int64_t w = py * rawImage->widthBytes();
|
int64_t w = py * rawImage->widthSamples();
|
||||||
int64_t w2 = w;
|
int64_t w2 = w;
|
||||||
if(py+1 < imgHeight)w2 += rawImage->widthBytes();
|
if(py+1 < imgHeight)w2 += rawImage->widthSamples();
|
||||||
if(py >= imgHeight)break;
|
if(py >= imgHeight)break;
|
||||||
|
|
||||||
for(int64_t x = std::max((int64_t)0, -ox); x < width; x++)
|
for(int64_t x = std::max((int64_t)0, -ox); x < width; x++)
|
||||||
@@ -486,6 +500,18 @@ void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scal
|
|||||||
painter.drawImage(0, 0, img);
|
painter.drawImage(0, 0, img);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageWidgetGL::drawGrid(bool enable)
|
||||||
|
{
|
||||||
|
if(m_grid.empty && m_wcs)
|
||||||
|
m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
|
||||||
|
|
||||||
|
if(enable != m_drawGrid)
|
||||||
|
{
|
||||||
|
m_drawGrid = enable;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ImageWidgetGL::paintGL()
|
void ImageWidgetGL::paintGL()
|
||||||
{
|
{
|
||||||
float dx = m_dx;
|
float dx = m_dx;
|
||||||
@@ -600,11 +626,46 @@ void ImageWidgetGL::paintGL()
|
|||||||
m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1);
|
m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1);
|
||||||
m_program->setUniformValue("lut_table", 2);
|
m_program->setUniformValue("lut_table", 2);
|
||||||
m_program->setUniformValue("srgb", m_srgb);
|
m_program->setUniformValue("srgb", m_srgb);
|
||||||
|
m_program->setUniformValue("colormapIdx", m_colormapIdx);
|
||||||
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
m_vao->release();
|
m_vao->release();
|
||||||
|
|
||||||
|
if(m_drawGrid && !m_grid.empty)
|
||||||
|
{
|
||||||
|
QPainter painter(this);
|
||||||
|
painter.setRenderHint(QPainter::Antialiasing);
|
||||||
|
painter.setRenderHint(QPainter::TextAntialiasing);
|
||||||
|
|
||||||
|
painter.setPen(QPen(Qt::yellow, 1.0 / m_scale));
|
||||||
|
QTransform tran;
|
||||||
|
tran.translate(-std::floor(dx), -std::floor(dy));
|
||||||
|
tran.scale(m_scale, m_scale);
|
||||||
|
painter.setTransform(tran);
|
||||||
|
painter.setClipRect(0, 0, m_imgWidth, m_imgHeight);
|
||||||
|
painter.drawPath(m_grid.grid);
|
||||||
|
painter.setPen(Qt::yellow);
|
||||||
|
QFont font({"Arial", "serif-sans"});
|
||||||
|
font.setPointSizeF(12 / m_scale);
|
||||||
|
painter.setFont(font);
|
||||||
|
for(auto &text : m_grid.text)
|
||||||
|
painter.drawText(QRectF(text.first, QSizeF(4000, 4000)), text.second);
|
||||||
|
|
||||||
|
painter.setPen(QPen(Qt::green, 1.0 / m_scale));
|
||||||
|
QFontMetricsF fontMetric = QFontMetricsF(font);
|
||||||
|
for(auto &object : m_grid.objects)
|
||||||
|
{
|
||||||
|
QRectF rect = fontMetric.boundingRect(object.name);
|
||||||
|
rect.moveCenter(object.pixel);
|
||||||
|
painter.setTransform(tran);
|
||||||
|
painter.drawText(rect, Qt::TextDontClip, object.name);
|
||||||
|
|
||||||
|
painter.translate(object.pixel);
|
||||||
|
painter.rotate(object.pos_ang);
|
||||||
|
painter.drawEllipse(QPointF(0, 0), object.maj_ax, object.maj_ax);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageWidgetGL::resizeGL(int w, int h)
|
void ImageWidgetGL::resizeGL(int w, int h)
|
||||||
@@ -660,19 +721,19 @@ void ImageWidgetGL::initializeGL()
|
|||||||
logger->startLogging();
|
logger->startLogging();
|
||||||
connect(logger, &QOpenGLDebugLogger::messageLogged, [](const QOpenGLDebugMessage &message)
|
connect(logger, &QOpenGLDebugLogger::messageLogged, [](const QOpenGLDebugMessage &message)
|
||||||
{
|
{
|
||||||
qDebug() << message;
|
qDebug() << "OpenGL debug" << message;
|
||||||
});
|
});
|
||||||
|
|
||||||
qDebug() << "Vendor:" << (char*)f->glGetString(GL_VENDOR);
|
qDebug() << "OpenGL Vendor:" << (char*)f->glGetString(GL_VENDOR);
|
||||||
qDebug() << "Renderer:" << (char*)f->glGetString(GL_RENDERER);
|
qDebug() << "OpenGL Renderer:" << (char*)f->glGetString(GL_RENDERER);
|
||||||
qDebug() << "Version:" << (char*)f->glGetString(GL_VERSION);
|
qDebug() << "OpenGL Version:" << (char*)f->glGetString(GL_VERSION);
|
||||||
f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize);
|
f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize);
|
||||||
f->glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &m_maxArrayLayers);
|
f->glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &m_maxArrayLayers);
|
||||||
qDebug() << "Max texture size:" << m_maxTextureSize << "max layers:" << m_maxArrayLayers;
|
qDebug() << "Max texture size:" << m_maxTextureSize << "max layers:" << m_maxArrayLayers;
|
||||||
|
|
||||||
//MANUAL_MIPMAP_GEN = QString((const char*)f->glGetString(GL_VENDOR)).startsWith("ATI Technologies Inc", Qt::CaseInsensitive);
|
//MANUAL_MIPMAP_GEN = QString((const char*)f->glGetString(GL_VENDOR)).startsWith("ATI Technologies Inc", Qt::CaseInsensitive);
|
||||||
|
|
||||||
qDebug() << context()->format();
|
qDebug() << "OpenGL context format" << context()->format();
|
||||||
|
|
||||||
// each vertex is x,y 2D position and s,t texture coordinates
|
// each vertex is x,y 2D position and s,t texture coordinates
|
||||||
float vertexs[] = {-1.0f, -1.0f, 0.0f, 1.0f,
|
float vertexs[] = {-1.0f, -1.0f, 0.0f, 1.0f,
|
||||||
@@ -702,6 +763,7 @@ void ImageWidgetGL::initializeGL()
|
|||||||
m_program->setAttributeBuffer("qt_MultiTexCoord0", GL_FLOAT, sizeof(float)*2, 2, sizeof(float)*4);
|
m_program->setAttributeBuffer("qt_MultiTexCoord0", GL_FLOAT, sizeof(float)*2, 2, sizeof(float)*4);
|
||||||
m_program->setUniformValue("qt_Texture0", (GLuint)0);
|
m_program->setUniformValue("qt_Texture0", (GLuint)0);
|
||||||
m_program->setUniformValue("lut_table", (GLuint)2);
|
m_program->setUniformValue("lut_table", (GLuint)2);
|
||||||
|
m_program->setUniformValue("colormap", (GLuint)3);
|
||||||
m_program->setUniformValue("scale", 1.0f, 0.0f);
|
m_program->setUniformValue("scale", 1.0f, 0.0f);
|
||||||
|
|
||||||
m_debayerProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
|
m_debayerProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
|
||||||
@@ -764,6 +826,19 @@ void ImageWidgetGL::initializeGL()
|
|||||||
m_lut->allocateStorage();
|
m_lut->allocateStorage();
|
||||||
m_lut->bind(2);
|
m_lut->bind(2);
|
||||||
|
|
||||||
|
QImage colormap = loadColormap();
|
||||||
|
m_colormap = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target2DArray);
|
||||||
|
m_colormap->setSize(colormap.width());
|
||||||
|
m_colormap->setLayers(colormap.height());
|
||||||
|
m_colormap->setFormat(QOpenGLTexture::RGBA8_UNorm);
|
||||||
|
m_colormap->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
|
||||||
|
m_colormap->setWrapMode(QOpenGLTexture::ClampToEdge);
|
||||||
|
m_colormap->allocateStorage();
|
||||||
|
for(int i=0; i<colormap.height(); i++)
|
||||||
|
m_colormap->setData(0, i, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, colormap.scanLine(i));
|
||||||
|
|
||||||
|
m_colormap->bind(3);
|
||||||
|
|
||||||
if(m_rawImage)
|
if(m_rawImage)
|
||||||
setImage(m_rawImage, m_currentImg);
|
setImage(m_rawImage, m_currentImg);
|
||||||
}
|
}
|
||||||
@@ -945,3 +1020,23 @@ void ImageWidgetGL::updateScrollBars()
|
|||||||
else
|
else
|
||||||
emit scrollBarsUpdate(m_dx, m_width, m_imgWidth * m_scale - m_width, m_dy, m_height, m_imgHeight * m_scale - m_height);
|
emit scrollBarsUpdate(m_dx, m_width, m_imgWidth * m_scale - m_width, m_dy, m_height, m_imgHeight * m_scale - m_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QImage ImageWidget::loadColormap()
|
||||||
|
{
|
||||||
|
QImage embedded(":/colormap.png");
|
||||||
|
QStringList path = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||||
|
if(path.size())
|
||||||
|
{
|
||||||
|
QImage user(path.first() + "/colormap.png");
|
||||||
|
if(!user.isNull())
|
||||||
|
{
|
||||||
|
user = user.scaledToWidth(embedded.width(), Qt::SmoothTransformation);
|
||||||
|
QImage tmp(embedded.width(), embedded.height() + user.height(), QImage::Format_RGBA8888);
|
||||||
|
QPainter painter(&tmp);
|
||||||
|
painter.drawImage(0, 0, embedded);
|
||||||
|
painter.drawImage(0, embedded.height(), user);
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return embedded.convertToFormat(QImage::Format_RGBA8888);
|
||||||
|
}
|
||||||
@@ -8,9 +8,10 @@
|
|||||||
#include <QOpenGLTexture>
|
#include <QOpenGLTexture>
|
||||||
#include <QOpenGLVertexArrayObject>
|
#include <QOpenGLVertexArrayObject>
|
||||||
#include <QOpenGLFunctions>
|
#include <QOpenGLFunctions>
|
||||||
|
#include <QPainterPath>
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "rawimage.h"
|
#include "rawimage.h"
|
||||||
#include "imageinfo.h"
|
#include "imageinfodata.h"
|
||||||
#include "stretchtoolbar.h"
|
#include "stretchtoolbar.h"
|
||||||
|
|
||||||
class ImageWidget
|
class ImageWidget
|
||||||
@@ -26,6 +27,7 @@ public:
|
|||||||
virtual void bestFit() = 0;
|
virtual void bestFit() = 0;
|
||||||
|
|
||||||
virtual void setBayerMask(int mask) = 0;
|
virtual void setBayerMask(int mask) = 0;
|
||||||
|
virtual void setColormap(int colormap) = 0;
|
||||||
virtual void setOffset(float dx, float dy) = 0;
|
virtual void setOffset(float dx, float dy) = 0;
|
||||||
virtual void allocateThumbnails(const QStringList &paths) = 0;
|
virtual void allocateThumbnails(const QStringList &paths) = 0;
|
||||||
|
|
||||||
@@ -36,6 +38,9 @@ public:
|
|||||||
virtual QImage renderToImage() = 0;
|
virtual QImage renderToImage() = 0;
|
||||||
virtual void thumbnailLoaded(const Image *image) = 0;
|
virtual void thumbnailLoaded(const Image *image) = 0;
|
||||||
virtual void showThumbnail(bool enable) = 0;
|
virtual void showThumbnail(bool enable) = 0;
|
||||||
|
virtual void drawGrid(bool enable) = 0;
|
||||||
|
|
||||||
|
static QImage loadColormap();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ImageThumb
|
struct ImageThumb
|
||||||
@@ -63,9 +68,11 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
|
|||||||
std::unique_ptr<QOpenGLVertexArrayObject> m_vaoThumb;
|
std::unique_ptr<QOpenGLVertexArrayObject> m_vaoThumb;
|
||||||
std::unique_ptr<QOpenGLTexture> m_thumbnailTexture;
|
std::unique_ptr<QOpenGLTexture> m_thumbnailTexture;
|
||||||
std::unique_ptr<QOpenGLTexture> m_lut;
|
std::unique_ptr<QOpenGLTexture> m_lut;
|
||||||
|
std::unique_ptr<QOpenGLTexture> m_colormap;
|
||||||
GLuint m_debayerTex = 0;
|
GLuint m_debayerTex = 0;
|
||||||
std::shared_ptr<RawImage> m_rawImage;
|
std::shared_ptr<RawImage> m_rawImage;
|
||||||
std::shared_ptr<WCSDataT> m_wcs;
|
std::shared_ptr<WCSDataT> m_wcs;
|
||||||
|
SkyGrid m_grid;
|
||||||
int m_width, m_height;
|
int m_width, m_height;
|
||||||
int m_imgWidth = -1, m_imgHeight = -1;
|
int m_imgWidth = -1, m_imgHeight = -1;
|
||||||
int m_currentImg = 0;
|
int m_currentImg = 0;
|
||||||
@@ -83,10 +90,12 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
|
|||||||
bool m_selecting = false;
|
bool m_selecting = false;
|
||||||
bool m_sizesDirty = false;
|
bool m_sizesDirty = false;
|
||||||
bool m_srgb = false;
|
bool m_srgb = false;
|
||||||
|
bool m_drawGrid = false;
|
||||||
int m_thumbnailCount = 0;
|
int m_thumbnailCount = 0;
|
||||||
int m_maxTextureSize = 0;
|
int m_maxTextureSize = 0;
|
||||||
int m_maxArrayLayers = 0;
|
int m_maxArrayLayers = 0;
|
||||||
int m_firstRed[2] = {0, 0};
|
int m_firstRed[2] = {0, 0};
|
||||||
|
int m_colormapIdx = 0;
|
||||||
QVector<ImageThumb> m_thumnails;
|
QVector<ImageThumb> m_thumnails;
|
||||||
Database *m_database = nullptr;
|
Database *m_database = nullptr;
|
||||||
QPointF m_lastPos;
|
QPointF m_lastPos;
|
||||||
@@ -102,6 +111,7 @@ public:
|
|||||||
void allocateThumbnails(const QStringList &paths) override;
|
void allocateThumbnails(const QStringList &paths) override;
|
||||||
QVector2D getImagePixelCoord(const QVector2D &pos);
|
QVector2D getImagePixelCoord(const QVector2D &pos);
|
||||||
void setBayerMask(int mask) override;
|
void setBayerMask(int mask) override;
|
||||||
|
void setColormap(int colormap) override;
|
||||||
void setOffset(float dx, float dy) override;
|
void setOffset(float dx, float dy) override;
|
||||||
void setMTFParams(const MTFParam ¶ms) override;
|
void setMTFParams(const MTFParam ¶ms) override;
|
||||||
void superPixel(bool enable) override;
|
void superPixel(bool enable) override;
|
||||||
@@ -110,6 +120,7 @@ public:
|
|||||||
QImage renderToImage() override;
|
QImage renderToImage() override;
|
||||||
void thumbnailLoaded(const Image *image) override;
|
void thumbnailLoaded(const Image *image) override;
|
||||||
void showThumbnail(bool enable) override;
|
void showThumbnail(bool enable) override;
|
||||||
|
void drawGrid(bool enable) override;
|
||||||
protected:
|
protected:
|
||||||
void paintGL() override;
|
void paintGL() override;
|
||||||
void resizeGL(int w, int h) override;
|
void resizeGL(int w, int h) override;
|
||||||
@@ -0,0 +1,498 @@
|
|||||||
|
#include "loadimage.h"
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDir>
|
||||||
|
#include <libraw/libraw.h>
|
||||||
|
#include <fitsio2.h>
|
||||||
|
#include "libxisf.h"
|
||||||
|
#include <libexif/exif-data.h>
|
||||||
|
#include "rawimage.h"
|
||||||
|
|
||||||
|
QString makeUNCPath(const QString &path)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN64
|
||||||
|
if(!path.startsWith("\\\\") && !path.startsWith("//"))
|
||||||
|
{
|
||||||
|
QString tmp;
|
||||||
|
QFileInfo info(path);
|
||||||
|
tmp = info.absoluteFilePath();
|
||||||
|
tmp = QDir::toNativeSeparators(tmp);
|
||||||
|
tmp.prepend("\\\\?\\");
|
||||||
|
qDebug() << "makeMaxPath" << path << tmp;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
int loadFITSHeader(fitsfile *file, ImageInfoData &info)
|
||||||
|
{
|
||||||
|
int imgtype;
|
||||||
|
int naxis;
|
||||||
|
long naxes[3] = {0};
|
||||||
|
int nexist;
|
||||||
|
int status = 0;
|
||||||
|
char key[FLEN_KEYWORD];
|
||||||
|
char val[FLEN_VALUE];
|
||||||
|
char comm[FLEN_COMMENT];
|
||||||
|
char strval[FLEN_VALUE];
|
||||||
|
QVariant var;
|
||||||
|
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
|
||||||
|
fits_get_hdrspace(file, &nexist, nullptr, &status);
|
||||||
|
for(int i=1; i<=nexist; i++)
|
||||||
|
{
|
||||||
|
fits_read_keyn(file, i, key, val, comm, &status);
|
||||||
|
fits_read_key(file, TSTRING, key, strval, nullptr, &status);
|
||||||
|
if(status == 0 || status == VALUE_UNDEFINED)
|
||||||
|
{
|
||||||
|
QString string(strval);
|
||||||
|
bool isint;
|
||||||
|
bool isdouble;
|
||||||
|
double vald = string.toDouble(&isdouble);
|
||||||
|
long long vall = string.toLongLong(&isint);
|
||||||
|
if(isint)
|
||||||
|
var = vall;
|
||||||
|
else if(isdouble)
|
||||||
|
var = vald;
|
||||||
|
else if(status == VALUE_UNDEFINED)
|
||||||
|
var = QVariant();
|
||||||
|
else if(string == "T" || string == "F")
|
||||||
|
var = string == "T";
|
||||||
|
else
|
||||||
|
var = string;
|
||||||
|
status = 0;
|
||||||
|
info.fitsHeader.append(FITSRecord(key, var, comm));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *header = nullptr;
|
||||||
|
int nrec = 0;
|
||||||
|
const char *exclist[] = {"PV1_1", "PV1_2"};
|
||||||
|
fits_hdr2str(file, TRUE, (char**)exclist, 2, &header, &nrec, &status);
|
||||||
|
if(status == 0)
|
||||||
|
{
|
||||||
|
info.wcs = std::make_shared<WCSDataT>(naxes[0], naxes[1], header, nrec);
|
||||||
|
if(!info.wcs->valid())info.wcs.reset();
|
||||||
|
}
|
||||||
|
fits_free_memory(header, &status);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar, uint32_t index)
|
||||||
|
{
|
||||||
|
fitsfile *file;
|
||||||
|
int status = 0;
|
||||||
|
int num = 0;
|
||||||
|
long naxes[3] = {0};
|
||||||
|
|
||||||
|
auto checkError = [&info, &status]()
|
||||||
|
{
|
||||||
|
char err[100];
|
||||||
|
fits_get_errstatus(status, err);
|
||||||
|
info.info.append({QObject::tr("Error"), QString(err)});
|
||||||
|
qWarning() << "Failed to load FITS file" << err;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
fits_open_diskfile(&file, path.toLocal8Bit().data(), READONLY, &status);
|
||||||
|
if(status)return checkError();
|
||||||
|
fits_get_num_hdus(file, &num, &status);
|
||||||
|
if(status)return checkError();
|
||||||
|
|
||||||
|
int hdutype;
|
||||||
|
int imgtype;
|
||||||
|
int naxis;
|
||||||
|
std::vector<int> imageIdxs;
|
||||||
|
for(int i = 1; i <= num; i++)
|
||||||
|
{
|
||||||
|
fits_movabs_hdu(file, i, &hdutype, &status);if(status)return checkError();
|
||||||
|
if(hdutype == IMAGE_HDU)
|
||||||
|
{
|
||||||
|
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);if(status)return checkError();
|
||||||
|
if(naxis >= 2 && naxis <= 3)imageIdxs.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info.num = imageIdxs.size();
|
||||||
|
info.index = index;
|
||||||
|
|
||||||
|
if(index >= imageIdxs.size())return false;
|
||||||
|
|
||||||
|
fits_movabs_hdu(file, imageIdxs[index], &hdutype, &status);if(status)return checkError();
|
||||||
|
if(hdutype == IMAGE_HDU)
|
||||||
|
{
|
||||||
|
naxes[0] = naxes[1] = naxes[2] = 0;
|
||||||
|
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);if(status)return checkError();
|
||||||
|
fits_get_img_equivtype(file, &imgtype, &status);if(status)return checkError();
|
||||||
|
|
||||||
|
if(hdutype == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
|
||||||
|
{
|
||||||
|
RawImage::DataType type;
|
||||||
|
int fitstype;
|
||||||
|
long fpixel[3] = {1,1,1};
|
||||||
|
switch(imgtype)
|
||||||
|
{
|
||||||
|
case BYTE_IMG:
|
||||||
|
type = RawImage::UINT8;
|
||||||
|
fitstype = TBYTE;
|
||||||
|
break;
|
||||||
|
case SHORT_IMG:
|
||||||
|
type = RawImage::UINT16;
|
||||||
|
fitstype = TSHORT;
|
||||||
|
break;
|
||||||
|
case USHORT_IMG:
|
||||||
|
type = RawImage::UINT16;
|
||||||
|
fitstype = TUSHORT;
|
||||||
|
break;
|
||||||
|
case LONG_IMG:
|
||||||
|
type = RawImage::UINT32;
|
||||||
|
fitstype = TINT;
|
||||||
|
break;
|
||||||
|
case ULONG_IMG:
|
||||||
|
type = RawImage::UINT32;
|
||||||
|
fitstype = TUINT;
|
||||||
|
break;
|
||||||
|
case FLOAT_IMG:
|
||||||
|
type = RawImage::FLOAT32;
|
||||||
|
fitstype = TFLOAT;
|
||||||
|
break;
|
||||||
|
case DOUBLE_IMG:
|
||||||
|
type = RawImage::FLOAT64;
|
||||||
|
fitstype = TDOUBLE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
|
||||||
|
goto noload;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = naxes[0]*naxes[1];
|
||||||
|
size_t w = naxes[0];
|
||||||
|
size_t h = naxes[1];
|
||||||
|
|
||||||
|
info.info.append({QObject::tr("Width"), QString::number(naxes[0])});
|
||||||
|
info.info.append({QObject::tr("Height"), QString::number(naxes[1])});
|
||||||
|
|
||||||
|
RawImage img(w, h, naxis == 2 ? 1 : naxes[2], type);
|
||||||
|
uint8_t *data = static_cast<uint8_t*>(img.data());
|
||||||
|
for (int i=1; i==1 || i<=naxes[2]; i++)
|
||||||
|
{
|
||||||
|
fpixel[2] = i;
|
||||||
|
fits_read_pix(file, fitstype, fpixel, size, NULL, data + img.size() * RawImage::typeSize(type) * (i-1), NULL, &status);
|
||||||
|
if(status)return checkError();
|
||||||
|
}
|
||||||
|
if(fitstype == TSHORT)
|
||||||
|
{
|
||||||
|
uint16_t *s = static_cast<uint16_t*>(img.data());
|
||||||
|
size_t size = img.size() * img.channels();
|
||||||
|
for(size_t i=0; i<size; i++)
|
||||||
|
s[i] -= INT16_MIN;
|
||||||
|
}
|
||||||
|
else if(fitstype == TINT)
|
||||||
|
{
|
||||||
|
uint32_t *s = static_cast<uint32_t*>(img.data());
|
||||||
|
size_t size = img.size() * img.channels();
|
||||||
|
for(size_t i=0; i<size; i++)
|
||||||
|
s[i] -= INT32_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(img.channels() == 1 || planar)
|
||||||
|
image = std::make_shared<RawImage>(std::move(img));
|
||||||
|
else
|
||||||
|
image = RawImage::fromPlanar(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
noload:
|
||||||
|
if(file)
|
||||||
|
{
|
||||||
|
status = loadFITSHeader(file, info);
|
||||||
|
if(status)return checkError();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(image)
|
||||||
|
{
|
||||||
|
for(auto fits : info.fitsHeader)
|
||||||
|
{
|
||||||
|
if(fits.key == "ROWORDER" && fits.value == "BOTTOM-UP")
|
||||||
|
image->flip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fits_close_file(file, &status);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar, uint32_t index)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LibXISF::XISFReader xisf;
|
||||||
|
xisf.open(path.toLocal8Bit().data());
|
||||||
|
|
||||||
|
if(index >= (uint32_t)xisf.imagesCount())return false;
|
||||||
|
const LibXISF::Image &xisfImage = xisf.getImage(index);
|
||||||
|
|
||||||
|
auto fitskeywords = xisfImage.fitsKeywords();
|
||||||
|
for(auto fits : fitskeywords)
|
||||||
|
{
|
||||||
|
info.fitsHeader.append(fits);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<FITSRecord> xisfWCS;
|
||||||
|
auto imageproperties = xisfImage.imageProperties();
|
||||||
|
for(auto prop : imageproperties)
|
||||||
|
{
|
||||||
|
info.fitsHeader.append(prop);
|
||||||
|
if(prop.id == "PCL:AstrometricSolution:ReferenceCelestialCoordinates" && prop.value.type() == LibXISF::Variant::Type::F64Vector)
|
||||||
|
{
|
||||||
|
auto val = prop.value.value<LibXISF::F64Vector>();
|
||||||
|
if(val.size() >= 2)
|
||||||
|
{
|
||||||
|
xisfWCS.append({"CRVAL1", val[0], "value from PCL:AstrometricSolution"});
|
||||||
|
xisfWCS.append({"CRVAL2", val[1], "value from PCL:AstrometricSolution"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(prop.id == "PCL:AstrometricSolution:ReferenceImageCoordinates" && prop.value.type() == LibXISF::Variant::Type::F64Vector)
|
||||||
|
{
|
||||||
|
auto val = prop.value.value<LibXISF::F64Vector>();
|
||||||
|
if(val.size() >= 2)
|
||||||
|
{
|
||||||
|
xisfWCS.append({"CRPIX1", val[0], "value from PCL:AstrometricSolution"});
|
||||||
|
xisfWCS.append({"CRPIX2", val[1], "value from PCL:AstrometricSolution"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(prop.id == "PCL:AstrometricSolution:LinearTransformationMatrix" && prop.value.type() == LibXISF::Variant::Type::F64Matrix)
|
||||||
|
{
|
||||||
|
auto val = prop.value.value<LibXISF::F64Matrix>();
|
||||||
|
if(val.cols() >= 2 && val.rows() >= 2)
|
||||||
|
{
|
||||||
|
xisfWCS.append({"CD1_1", val(0, 0), "value from PCL:AstrometricSolution"});
|
||||||
|
xisfWCS.append({"CD1_2", val(0, 1), "value from PCL:AstrometricSolution"});
|
||||||
|
xisfWCS.append({"CD2_1", val(1, 0), "value from PCL:AstrometricSolution"});
|
||||||
|
xisfWCS.append({"CD2_2", val(1, 1), "value from PCL:AstrometricSolution"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(prop.id == "PCL:AstrometricSolution:ProjectionSystem")
|
||||||
|
{
|
||||||
|
if(prop.value.toString() == "Gnomonic")
|
||||||
|
{
|
||||||
|
xisfWCS.append({"CTYPE1", "RA---TAN", "value from PCL:AstrometricSolution"});
|
||||||
|
xisfWCS.append({"CTYPE", "DEC--TAN", "value from PCL:AstrometricSolution"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info.num = xisf.imagesCount();
|
||||||
|
info.index = index;
|
||||||
|
info.info.append({QObject::tr("Width"), QString::number(xisfImage.width())});
|
||||||
|
info.info.append({QObject::tr("Height"), QString::number(xisfImage.height())});
|
||||||
|
|
||||||
|
auto wcs = std::make_shared<WCSDataT>(xisfImage.width(), xisfImage.height(), info.fitsHeader);
|
||||||
|
if(!wcs->valid() && xisfWCS.size())wcs = std::make_shared<WCSDataT>(xisfImage.width(), xisfImage.height(), xisfWCS);
|
||||||
|
if(wcs->valid())info.wcs = wcs;
|
||||||
|
|
||||||
|
RawImage::DataType type;
|
||||||
|
switch(xisfImage.sampleFormat())
|
||||||
|
{
|
||||||
|
case LibXISF::Image::UInt8: type = RawImage::UINT8; break;
|
||||||
|
case LibXISF::Image::UInt16: type = RawImage::UINT16; break;
|
||||||
|
case LibXISF::Image::UInt32: type = RawImage::UINT32; break;
|
||||||
|
case LibXISF::Image::Float32: type = RawImage::FLOAT32; break;
|
||||||
|
case LibXISF::Image::Float64: type = RawImage::FLOAT64; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LibXISF::Image tmpImage = xisfImage;
|
||||||
|
tmpImage.convertPixelStorageTo(LibXISF::Image::Planar);
|
||||||
|
if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray)
|
||||||
|
{
|
||||||
|
image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
|
||||||
|
std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
|
||||||
|
image->setICCProfile(tmpImage.iccProfile());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
|
||||||
|
{
|
||||||
|
if(planar)
|
||||||
|
{
|
||||||
|
image = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
|
||||||
|
std::memcpy(image->data(), tmpImage.imageData(), tmpImage.imageDataSize());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
image = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
image->setICCProfile(tmpImage.iccProfile());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (LibXISF::Error &err)
|
||||||
|
{
|
||||||
|
info.info.append(QPair<QString, QString>("Error", err.what()));
|
||||||
|
qWarning() << "Failed to load XISF" << err.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readFITSHeader(const QString &path, ImageInfoData &info)
|
||||||
|
{
|
||||||
|
fitsfile *fr;
|
||||||
|
int status = 0;
|
||||||
|
QString path2 = makeUNCPath(path);
|
||||||
|
fits_open_diskfile(&fr, path2.toLocal8Bit().data(), READONLY, &status);
|
||||||
|
|
||||||
|
if(fr && status == 0)
|
||||||
|
{
|
||||||
|
status = loadFITSHeader(fr, info);
|
||||||
|
fits_close_file(fr, &status);
|
||||||
|
}
|
||||||
|
return status == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readXISFHeader(const QString &path, ImageInfoData &info)
|
||||||
|
{
|
||||||
|
QString path2 = makeUNCPath(path);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LibXISF::XISFReader xisf;
|
||||||
|
xisf.open(path2.toLocal8Bit().data());
|
||||||
|
const LibXISF::Image &image = xisf.getImage(0, false);
|
||||||
|
|
||||||
|
auto fitskeywords = image.fitsKeywords();
|
||||||
|
for(auto fits : fitskeywords)
|
||||||
|
{
|
||||||
|
info.fitsHeader.append(fits);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto imageproperties = image.imageProperties();
|
||||||
|
for(auto prop : imageproperties)
|
||||||
|
{
|
||||||
|
info.fitsHeader.append(prop);
|
||||||
|
}
|
||||||
|
info.wcs = std::make_shared<WCSDataT>(image.width(), image.height(), info.fitsHeader);
|
||||||
|
if(!info.wcs->valid())info.wcs.reset();
|
||||||
|
}
|
||||||
|
catch (LibXISF::Error &err)
|
||||||
|
{
|
||||||
|
qWarning() << "LibXISF error" << err.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadExifEntry(ImageInfoData &info, ExifContent *content, ExifTag tag)
|
||||||
|
{
|
||||||
|
char val[1024];
|
||||||
|
ExifEntry *entry = exif_content_get_entry(content, tag);
|
||||||
|
if(entry)
|
||||||
|
{
|
||||||
|
exif_entry_get_value(entry, val, sizeof(val));
|
||||||
|
info.info.append({exif_tag_get_title(tag), val});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadRAW(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image)
|
||||||
|
{
|
||||||
|
std::unique_ptr<LibRaw> raw = std::make_unique<LibRaw>();
|
||||||
|
raw->open_file(path.toLocal8Bit().data());
|
||||||
|
raw->imgdata.params.half_size = true;
|
||||||
|
raw->imgdata.params.use_camera_wb = true;
|
||||||
|
raw->imgdata.params.user_flip = 0;
|
||||||
|
if(raw->unpack())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
libraw_rawdata_t rawdata = raw->imgdata.rawdata;
|
||||||
|
size_t size = rawdata.sizes.width*rawdata.sizes.height;
|
||||||
|
|
||||||
|
std::vector<uint16_t> out;
|
||||||
|
out.resize(size);
|
||||||
|
size_t d = 0;
|
||||||
|
uint h=rawdata.sizes.top_margin+rawdata.sizes.height;
|
||||||
|
uint w=rawdata.sizes.left_margin+rawdata.sizes.width;
|
||||||
|
size_t pitch = rawdata.sizes.raw_pitch/sizeof(uint16_t);
|
||||||
|
|
||||||
|
for(size_t i=rawdata.sizes.top_margin;i<h;i++)
|
||||||
|
{
|
||||||
|
for(size_t o=rawdata.sizes.left_margin;o<w;o++)
|
||||||
|
{
|
||||||
|
uint16_t p = rawdata.raw_image[i*pitch+o];
|
||||||
|
out[d++] = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image = std::make_shared<RawImage>(rawdata.sizes.width, rawdata.sizes.height, 1, RawImage::UINT16);
|
||||||
|
memcpy(image->data(), &out[0], sizeof(uint16_t)*d);
|
||||||
|
|
||||||
|
QString shutterSpeed = QString::number(raw->imgdata.other.shutter);
|
||||||
|
if(raw->imgdata.other.shutter < 1)
|
||||||
|
{
|
||||||
|
shutterSpeed = QString("1/%1s").arg(1.0f/raw->imgdata.other.shutter);
|
||||||
|
}
|
||||||
|
info.info.append({QObject::tr("Width"), QString::number(raw->imgdata.sizes.width)});
|
||||||
|
info.info.append({QObject::tr("Height"), QString::number(raw->imgdata.sizes.height)});
|
||||||
|
info.info.append({QObject::tr("ISO"), QString::number(raw->imgdata.other.iso_speed)});
|
||||||
|
info.info.append({QObject::tr("Shutter speed"), shutterSpeed});
|
||||||
|
#if LIBRAW_MINOR_VERSION>=19
|
||||||
|
// info.append(StringPair(QObject::tr("Camera temperature"), QString::number(raw.imgdata.other.CameraTemperature)));
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, int index, bool planar)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
QElapsedTimer timer;
|
||||||
|
QFileInfo fileInfo(path);
|
||||||
|
timer.start();
|
||||||
|
if(path.endsWith(".CR2", Qt::CaseInsensitive) || path.endsWith(".CR3", Qt::CaseInsensitive) || path.endsWith(".NEF", Qt::CaseInsensitive) || path.endsWith(".DNG", Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
ret = loadRAW(path, info, rawImage);
|
||||||
|
qDebug() << "LoadRAW" << timer.elapsed() << "ms";
|
||||||
|
}
|
||||||
|
else if(isFITS(fileInfo.suffix()))
|
||||||
|
{
|
||||||
|
ret = loadFITS(path, info, rawImage, planar, index);
|
||||||
|
qDebug() << "LoadFITS" << timer.elapsed() << "ms";
|
||||||
|
}
|
||||||
|
else if(isXISF(fileInfo.suffix()))
|
||||||
|
{
|
||||||
|
ret = loadXISF(path, info, rawImage, planar, index);
|
||||||
|
qDebug() << "LoadXISF" << timer.elapsed() << "ms";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QImage img(path);
|
||||||
|
|
||||||
|
ExifData *exif = exif_data_new_from_file(path.toLocal8Bit().constData());
|
||||||
|
info.info.append({QObject::tr("Width"), QString::number(img.width())});
|
||||||
|
info.info.append({QObject::tr("Height"), QString::number(img.height())});
|
||||||
|
if(exif)
|
||||||
|
{
|
||||||
|
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_ISO_SPEED_RATINGS);
|
||||||
|
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_SHUTTER_SPEED_VALUE);
|
||||||
|
exif_data_free(exif);
|
||||||
|
}
|
||||||
|
rawImage = std::make_shared<RawImage>(img);
|
||||||
|
qDebug() << "LoadQImage" << timer.elapsed() << "ms";
|
||||||
|
ret = !img.isNull();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFITS(const QString &suffix)
|
||||||
|
{
|
||||||
|
return suffix.compare("fits", Qt::CaseInsensitive) == 0 || suffix.compare("fit", Qt::CaseInsensitive) == 0 || suffix.compare("fts", Qt::CaseInsensitive) == 0 || suffix.compare("fz", Qt::CaseInsensitive) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isXISF(const QString &suffix)
|
||||||
|
{
|
||||||
|
return suffix.compare("xisf", Qt::CaseInsensitive) == 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef LOADIMAGE_H
|
||||||
|
#define LOADIMAGE_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include "imageinfodata.h"
|
||||||
|
|
||||||
|
class RawImage;
|
||||||
|
|
||||||
|
QString makeUNCPath(const QString &path);
|
||||||
|
bool readFITSHeader(const QString &path, ImageInfoData &info);
|
||||||
|
bool readXISFHeader(const QString &path, ImageInfoData &info);
|
||||||
|
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, int index, bool planar = false);
|
||||||
|
bool isFITS(const QString &suffix);
|
||||||
|
bool isXISF(const QString &suffix);
|
||||||
|
|
||||||
|
#endif // LOADIMAGE_H
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
#include "loadrunable.h"
|
||||||
|
#include "imageringlist.h"
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fitsio2.h>
|
||||||
|
#include "rawimage.h"
|
||||||
|
#include "loadimage.h"
|
||||||
|
#include <lcms2.h>
|
||||||
|
|
||||||
|
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, int index, bool thumbnail) :
|
||||||
|
m_file(makeUNCPath(file)),
|
||||||
|
m_receiver(receiver),
|
||||||
|
m_analyzeLevel(level),
|
||||||
|
m_thumbnail(thumbnail),
|
||||||
|
m_index(index)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadRunable::run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(!m_thumbnail && !m_receiver->isCurrent())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QElapsedTimer timer;
|
||||||
|
ImageInfoData info;
|
||||||
|
QFileInfo finfo(m_file);
|
||||||
|
info.info.append({QObject::tr("Filename"), finfo.fileName()});
|
||||||
|
|
||||||
|
std::shared_ptr<RawImage> rawImage;
|
||||||
|
if(!loadImage(m_file, info, rawImage, m_index))
|
||||||
|
info.info.append({QObject::tr("Error"), QObject::tr("Failed to load image")});
|
||||||
|
|
||||||
|
|
||||||
|
if(rawImage && !m_thumbnail)
|
||||||
|
{
|
||||||
|
rawImage->convertToGLFormat();
|
||||||
|
timer.start();
|
||||||
|
rawImage->generateLUT();
|
||||||
|
qDebug() << "generate LUT" << timer.restart();
|
||||||
|
//rawImage->convertTosRGB();
|
||||||
|
//qDebug() << "convert" << timer.restart();
|
||||||
|
rawImage->calcStats();
|
||||||
|
const RawImage::Stats &stats = rawImage->imageStats();
|
||||||
|
qDebug() << "image stats" << timer.restart();
|
||||||
|
if(rawImage->channels() == 1)
|
||||||
|
{
|
||||||
|
info.info.append({QObject::tr("Mean"), QString::number(stats.m_mean[0])});
|
||||||
|
info.info.append({QObject::tr("Standart deviation"), QString::number(stats.m_stdDev[0])});
|
||||||
|
info.info.append({QObject::tr("Median"), QString::number(stats.m_median[0])});
|
||||||
|
info.info.append({QObject::tr("Minimum"), QString::number(stats.m_min[0])});
|
||||||
|
info.info.append({QObject::tr("Maximum"), QString::number(stats.m_max[0])});
|
||||||
|
info.info.append({QObject::tr("MAD"), QString::number(stats.m_mad[0])});
|
||||||
|
info.info.append({QObject::tr("Saturated"), QString::number(100.0 * stats.m_saturated[0] / rawImage->size()) + "%"});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info.info.append({QObject::tr("Mean"), QString("%1 %2 %3").arg(stats.m_mean[0]).arg(stats.m_mean[1]).arg(stats.m_mean[2])});
|
||||||
|
info.info.append({QObject::tr("Standart deviation"), QString("%1 %2 %3").arg(stats.m_stdDev[0]).arg(stats.m_stdDev[1]).arg(stats.m_stdDev[2])});
|
||||||
|
info.info.append({QObject::tr("Median"), QString("%1 %2 %3").arg(stats.m_median[0]).arg(stats.m_median[1]).arg(stats.m_median[2])});
|
||||||
|
info.info.append({QObject::tr("Minimum"), QString("%1 %2 %3").arg(stats.m_min[0]).arg(stats.m_min[1]).arg(stats.m_min[2])});
|
||||||
|
info.info.append({QObject::tr("Maximum"), QString("%1 %2 %3").arg(stats.m_max[0]).arg(stats.m_max[1]).arg(stats.m_max[2])});
|
||||||
|
info.info.append({QObject::tr("MAD"), QString("%1 %2 %3").arg(stats.m_mad[0]).arg(stats.m_mad[1]).arg(stats.m_mad[2])});
|
||||||
|
info.info.append({QObject::tr("Saturated"), QString("%1 %2 %3%").arg(100.0 * stats.m_saturated[0] / rawImage->size())
|
||||||
|
.arg(100.0 * stats.m_saturated[1] / rawImage->size())
|
||||||
|
.arg(100.0 * stats.m_saturated[2] / rawImage->size())});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(m_thumbnail)
|
||||||
|
{
|
||||||
|
if(rawImage && rawImage->valid())
|
||||||
|
{
|
||||||
|
if(QUALITY_RESIZE)
|
||||||
|
rawImage->resize(THUMB_SIZE, THUMB_SIZE);
|
||||||
|
|
||||||
|
rawImage->convertToGLFormat();
|
||||||
|
rawImage->convertToThumbnail();
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(m_receiver, "imageLoaded", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage), Q_ARG(ImageInfoData, info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(std::exception e)
|
||||||
|
{
|
||||||
|
qDebug() << m_file << e.what();
|
||||||
|
std::shared_ptr<RawImage> rawImage;
|
||||||
|
if(m_thumbnail)
|
||||||
|
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage));
|
||||||
|
else
|
||||||
|
QMetaObject::invokeMethod(m_receiver, "imageLoaded", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage), Q_ARG(ImageInfoData, ImageInfoData()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConvertRunable::ConvertRunable(const QString &in, const QString &out, const QString &format, const ConvertParams ¶ms, QSemaphore *semaphore) :
|
||||||
|
m_infile(makeUNCPath(in)),
|
||||||
|
m_outfile(makeUNCPath(out)),
|
||||||
|
m_format(format),
|
||||||
|
m_params(params),
|
||||||
|
m_semaphore(semaphore)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeFITSImage(fitsfile *fw, std::shared_ptr<RawImage> rawimage, ImageInfoData &imageinfo)
|
||||||
|
{
|
||||||
|
static QStringList skipKeys = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "BZERO", "BSCALE", "EXTEND"};
|
||||||
|
|
||||||
|
int status = 0;
|
||||||
|
long firstpix[3] = {1,1,1};
|
||||||
|
|
||||||
|
int channels = rawimage->channels();
|
||||||
|
int naxis = channels == 1 ? 2 : 3;
|
||||||
|
long naxes[3] = {(int)rawimage->width(), (int)rawimage->height(), rawimage->channels()};
|
||||||
|
|
||||||
|
std::vector<RawImage> planes;
|
||||||
|
if(channels == 1)
|
||||||
|
planes.push_back(*rawimage);
|
||||||
|
else
|
||||||
|
planes = rawimage->split();
|
||||||
|
|
||||||
|
switch(rawimage->type())
|
||||||
|
{
|
||||||
|
case RawImage::UINT8:
|
||||||
|
fits_create_img(fw, BYTE_IMG, naxis, naxes, &status);
|
||||||
|
for(int i=0; i<channels; i++)
|
||||||
|
{
|
||||||
|
firstpix[2] = i+1;
|
||||||
|
fits_write_pix(fw, TBYTE, firstpix, rawimage->size(), planes[i].data(), &status);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RawImage::UINT16:
|
||||||
|
fits_create_img(fw, USHORT_IMG, naxis, naxes, &status);
|
||||||
|
for(int i=0; i<channels; i++)
|
||||||
|
{
|
||||||
|
firstpix[2] = i+1;
|
||||||
|
fits_write_pix(fw, TUSHORT, firstpix, rawimage->size(), planes[i].data(), &status);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RawImage::FLOAT32:
|
||||||
|
fits_create_img(fw, FLOAT_IMG, naxis, naxes, &status);
|
||||||
|
for(int i=0; i<channels; i++)
|
||||||
|
{
|
||||||
|
firstpix[2] = i+1;
|
||||||
|
fits_write_pix(fw, TFLOAT, firstpix, rawimage->size(), planes[i].data(), &status);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(const FITSRecord &record : imageinfo.fitsHeader)
|
||||||
|
{
|
||||||
|
if(skipKeys.contains(record.key) || record.xisf)continue;
|
||||||
|
|
||||||
|
bool isdouble;
|
||||||
|
bool isint;
|
||||||
|
bool isbool = record.value.toString() == "T" || record.value.toString() == "F";
|
||||||
|
double vald = record.value.toDouble(&isdouble);
|
||||||
|
int valb = record.value.toString() == "T";
|
||||||
|
long long vall = record.value.toLongLong(&isint);
|
||||||
|
if(isint)isint = vall == vald;
|
||||||
|
QByteArray str = record.value.toString().toLatin1();
|
||||||
|
if(isint)
|
||||||
|
fits_write_key(fw, TLONGLONG, record.key.data(), &vall, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||||
|
else if(isdouble)
|
||||||
|
fits_write_key(fw, TDOUBLE, record.key.data(), &vald, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||||
|
else if(isbool)
|
||||||
|
fits_write_key(fw, TLOGICAL, record.key.data(), &valb, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||||
|
else if(record.key == "COMMENT")
|
||||||
|
fits_write_comment(fw, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||||
|
else if(record.key == "HISTORY")
|
||||||
|
fits_write_history(fw, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||||
|
else
|
||||||
|
fits_write_key(fw, TSTRING, record.key.data(), str.isEmpty() ? nullptr : str.data(), record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConvertRunable::run()
|
||||||
|
{
|
||||||
|
QSemaphoreReleaser release;
|
||||||
|
if(m_semaphore)release = QSemaphoreReleaser(m_semaphore);
|
||||||
|
|
||||||
|
ImageInfoData imageinfo;
|
||||||
|
std::shared_ptr<RawImage> rawimage;
|
||||||
|
loadImage(m_infile, imageinfo, rawimage, 0);
|
||||||
|
QFileInfo info(m_outfile);
|
||||||
|
info.dir().mkpath(".");
|
||||||
|
|
||||||
|
if(m_params.stretch)
|
||||||
|
{
|
||||||
|
rawimage->applySTF(m_params.mtf);
|
||||||
|
}
|
||||||
|
else if(m_params.autostretch)
|
||||||
|
{
|
||||||
|
rawimage->calcStats();
|
||||||
|
MTFParam mtfParam = rawimage->calcMTFParams();
|
||||||
|
rawimage->applySTF(mtfParam);
|
||||||
|
}
|
||||||
|
if(m_params.binning > 1)
|
||||||
|
{
|
||||||
|
rawimage->resizeInt(m_params.binning, m_params.average);
|
||||||
|
}
|
||||||
|
if(m_params.resize.isValid() && !m_params.resize.isEmpty())
|
||||||
|
{
|
||||||
|
QSize imgSize(rawimage->width(), rawimage->height());
|
||||||
|
imgSize = imgSize.scaled(m_params.resize, m_params.aspect);
|
||||||
|
rawimage->resize(imgSize.width(), imgSize.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(rawimage)
|
||||||
|
{
|
||||||
|
if(m_format == "xisf")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LibXISF::XISFWriter xisf;
|
||||||
|
int channelCount = rawimage->channels();
|
||||||
|
LibXISF::Image::SampleFormat sampleFormat;
|
||||||
|
switch(rawimage->type())
|
||||||
|
{
|
||||||
|
case RawImage::UINT8: sampleFormat = LibXISF::Image::UInt8; break;
|
||||||
|
case RawImage::UINT16: sampleFormat = LibXISF::Image::UInt16; break;
|
||||||
|
case RawImage::FLOAT32: sampleFormat = LibXISF::Image::Float32; break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LibXISF::Image image(rawimage->width(), rawimage->height(), channelCount, sampleFormat, channelCount == 1 ? LibXISF::Image::Gray : LibXISF::Image::RGB, LibXISF::Image::Planar);
|
||||||
|
if(channelCount == 1)
|
||||||
|
{
|
||||||
|
std::memcpy(image.imageData(), rawimage->data(), image.imageDataSize());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size_t off = 0;
|
||||||
|
std::vector<RawImage> planes = rawimage->split();
|
||||||
|
for(const auto &plane : planes)
|
||||||
|
{
|
||||||
|
std::memcpy(image.imageData<uint8_t>() + off, plane.data(), plane.size() * RawImage::typeSize(plane.type()));
|
||||||
|
off += plane.size() * RawImage::typeSize(plane.type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(auto &record : imageinfo.fitsHeader)
|
||||||
|
{
|
||||||
|
if(record.xisf)continue;
|
||||||
|
|
||||||
|
if(record.value.typeId() == QMetaType::Bool)
|
||||||
|
image.addFITSKeyword({record.key.toStdString(), record.value.toBool() ? "T" : "F", record.comment.toStdString()});
|
||||||
|
else
|
||||||
|
image.addFITSKeyword({record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(m_params.compressionType.startsWith("zstd") && LibXISF::DataBlock::CompressionCodecSupported(LibXISF::DataBlock::ZSTD))
|
||||||
|
image.setCompression(LibXISF::DataBlock::ZSTD, m_params.compressionLevel);
|
||||||
|
else if(m_params.compressionType.startsWith("lz4hc"))
|
||||||
|
image.setCompression(LibXISF::DataBlock::LZ4HC, m_params.compressionLevel);
|
||||||
|
else if(m_params.compressionType.startsWith("lz4"))
|
||||||
|
image.setCompression(LibXISF::DataBlock::LZ4, m_params.compressionLevel);
|
||||||
|
else if(m_params.compressionType.startsWith("zlib"))
|
||||||
|
image.setCompression(LibXISF::DataBlock::Zlib, m_params.compressionLevel);
|
||||||
|
|
||||||
|
if(m_params.compressionType.endsWith("+sh"))
|
||||||
|
image.setByteshuffling(true);
|
||||||
|
|
||||||
|
xisf.writeImage(image);
|
||||||
|
xisf.save(m_outfile.toLocal8Bit().data());
|
||||||
|
}
|
||||||
|
catch(LibXISF::Error &err)
|
||||||
|
{
|
||||||
|
qDebug() << "Failed to save XISF image" << err.what();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(m_format == "fits")
|
||||||
|
{
|
||||||
|
int status = 0;
|
||||||
|
fitsfile *fw;
|
||||||
|
if(QFileInfo(m_outfile).exists())QFile::remove(m_outfile);
|
||||||
|
fits_create_diskfile(&fw, m_outfile.toLocal8Bit().data(), &status);
|
||||||
|
if(!m_params.compressionType.isEmpty())
|
||||||
|
{
|
||||||
|
if(m_params.compressionType == "gzip")
|
||||||
|
fits_set_compression_type(fw, GZIP_1, &status);
|
||||||
|
else if(m_params.compressionType == "rice")
|
||||||
|
fits_set_compression_type(fw, RICE_1, &status);
|
||||||
|
}
|
||||||
|
writeFITSImage(fw, rawimage, imageinfo);
|
||||||
|
fits_close_file(fw, &status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if nothing else try QImage
|
||||||
|
{
|
||||||
|
QImage::Format format = QImage::Format_Invalid;
|
||||||
|
|
||||||
|
switch(rawimage->type())
|
||||||
|
{
|
||||||
|
case RawImage::UINT8:
|
||||||
|
if(rawimage->channels() == 1)format = QImage::Format_Grayscale8;
|
||||||
|
else if(rawimage->channels() == 3)format = QImage::Format_RGBX8888;
|
||||||
|
else if(rawimage->channels() == 4)format = QImage::Format_RGBA8888;
|
||||||
|
break;
|
||||||
|
case RawImage::UINT16:
|
||||||
|
if(rawimage->channels() == 1)format = QImage::Format_Grayscale16;
|
||||||
|
else if(rawimage->channels() == 3)format = QImage::Format_RGBX64;
|
||||||
|
else if(rawimage->channels() == 4)format = QImage::Format_RGBA64;
|
||||||
|
break;
|
||||||
|
case RawImage::FLOAT16:
|
||||||
|
case RawImage::FLOAT32:
|
||||||
|
case RawImage::FLOAT64:
|
||||||
|
case RawImage::UINT32:
|
||||||
|
rawimage->convertToType(RawImage::UINT16);
|
||||||
|
if(rawimage->channels() == 1)format = QImage::Format_Grayscale16;
|
||||||
|
else if(rawimage->channels() == 3)format = QImage::Format_RGBX64;
|
||||||
|
else if(rawimage->channels() == 4)format = QImage::Format_RGBA64;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(format == QImage::Format_Invalid)return;
|
||||||
|
|
||||||
|
QImage qimage((const uchar*)rawimage->data(), rawimage->width(), rawimage->height(), rawimage->widthBytes(), format);
|
||||||
|
qimage.save(m_outfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConvertRunable::ConvertParams::ConvertParams(const QVariantMap &map)
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
if(map.contains("compressionLevel"))
|
||||||
|
compressionLevel = std::clamp(map["compressionLevel"].toInt(&ok), -1, 100);
|
||||||
|
|
||||||
|
if(!ok)compressionLevel = -1;
|
||||||
|
|
||||||
|
if(map.contains("compressionType"))
|
||||||
|
compressionType = map["compressionType"].toString();
|
||||||
|
|
||||||
|
if(map.contains("binning"))
|
||||||
|
binning = map["binning"].toInt();
|
||||||
|
|
||||||
|
if(map.contains("average"))
|
||||||
|
average = map["average"].toBool();
|
||||||
|
|
||||||
|
if(map.contains("resize"))
|
||||||
|
{
|
||||||
|
QVariantMap size = map["resize"].toMap();
|
||||||
|
if(size.contains("width") && size.contains("height"))
|
||||||
|
{
|
||||||
|
int w = size["width"].toInt();
|
||||||
|
int h = size["height"].toInt();
|
||||||
|
resize = QSize(w, h);
|
||||||
|
}
|
||||||
|
if(size.contains("aspect"))
|
||||||
|
{
|
||||||
|
QString aspectStr = map["aspect"].toString();
|
||||||
|
if(aspectStr == "keep")
|
||||||
|
aspect = Qt::KeepAspectRatio;
|
||||||
|
else if(aspectStr == "expand")
|
||||||
|
aspect = Qt::KeepAspectRatioByExpanding;
|
||||||
|
else if(aspectStr == "ignore")
|
||||||
|
aspect = Qt::IgnoreAspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(map.contains("autostretch"))
|
||||||
|
autostretch = map["autostretch"].toBool();
|
||||||
|
}
|
||||||
@@ -4,14 +4,9 @@
|
|||||||
#include <QRunnable>
|
#include <QRunnable>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QSemaphore>
|
#include <QSemaphore>
|
||||||
#include "imageinfo.h"
|
#include <QSize>
|
||||||
|
#include "imageinfodata.h"
|
||||||
class RawImage;
|
#include "mtfparam.h"
|
||||||
|
|
||||||
QString makeMaxPath(QString path);
|
|
||||||
bool readFITSHeader(const QString &path, ImageInfoData &info);
|
|
||||||
bool readXISFHeader(const QString &path, ImageInfoData &info);
|
|
||||||
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar = false);
|
|
||||||
|
|
||||||
class Image;
|
class Image;
|
||||||
|
|
||||||
@@ -21,12 +16,12 @@ class LoadRunable : public QRunnable
|
|||||||
Image *m_receiver;
|
Image *m_receiver;
|
||||||
AnalyzeLevel m_analyzeLevel;
|
AnalyzeLevel m_analyzeLevel;
|
||||||
bool m_thumbnail;
|
bool m_thumbnail;
|
||||||
|
int m_index = 0;
|
||||||
public:
|
public:
|
||||||
LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail = false);
|
LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, int index, bool thumbnail = false);
|
||||||
void run() override;
|
void run() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class ConvertRunable : public QRunnable
|
class ConvertRunable : public QRunnable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -34,6 +29,13 @@ public:
|
|||||||
{
|
{
|
||||||
int compressionLevel = -1;
|
int compressionLevel = -1;
|
||||||
QString compressionType;
|
QString compressionType;
|
||||||
|
int binning = 0;
|
||||||
|
bool average = true;
|
||||||
|
QSize resize;
|
||||||
|
Qt::AspectRatioMode aspect = Qt::KeepAspectRatio;
|
||||||
|
bool autostretch = false;
|
||||||
|
bool stretch = false;
|
||||||
|
MTFParam mtf;
|
||||||
ConvertParams(){}
|
ConvertParams(){}
|
||||||
ConvertParams(const QVariantMap &map);
|
ConvertParams(const QVariantMap &map);
|
||||||
};
|
};
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
#include "mainwindow.h"
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QSurfaceFormat>
|
||||||
|
#include <QTranslator>
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "../thumbnailer/genthumbnail.h"
|
||||||
|
#ifdef Q_OS_WIN64
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
#ifdef __linux__
|
||||||
|
setenv("LC_NUMERIC", "C", 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__i386__) || defined(__x86_64__) || defined(__APPLE__)
|
||||||
|
bool useGLES = false;
|
||||||
|
#else
|
||||||
|
bool useGLES = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN64
|
||||||
|
if(AttachConsole(ATTACH_PARENT_PROCESS))
|
||||||
|
{
|
||||||
|
freopen("CONOUT$", "w", stdout);
|
||||||
|
freopen("CONOUT$", "w", stderr);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QCommandLineParser cmd;
|
||||||
|
cmd.addOption({"gl", "Use desktop OpenGL. This is default on x86 and MacOS platform."});
|
||||||
|
cmd.addOption({"gles", "Use OpenGL ES. This is default on ARM platform."});
|
||||||
|
cmd.addOption({{"thumb", "thumbnail"}, "Generate thumbnail and save it to path.", "path"});
|
||||||
|
cmd.addOption({{"s", "size"}, "Size of the thumbnails in pixels (default: 128)", "size", "128"});
|
||||||
|
cmd.addPositionalArgument("file", "Files or paths to open");
|
||||||
|
cmd.addOption({"script", "Execute script", "script"});
|
||||||
|
cmd.addOption({"scriptarg", "String that will be passed to script as variable \"scriparg\"", "arg"});
|
||||||
|
cmd.addOption({"outdir", "Output dir for script (default: CWD)", "dir", "."});
|
||||||
|
cmd.addOption({"noexit", "Do not exit application when script finish"});
|
||||||
|
cmd.addHelpOption();
|
||||||
|
QStringList cmdArgs;
|
||||||
|
for(int i = 0; i < argc; i++)
|
||||||
|
cmdArgs.append(argv[i]);
|
||||||
|
|
||||||
|
cmd.process(cmdArgs);
|
||||||
|
if(cmd.isSet("gl"))
|
||||||
|
useGLES = false;
|
||||||
|
if(cmd.isSet("gles"))
|
||||||
|
useGLES = true;
|
||||||
|
|
||||||
|
if(cmd.isSet("thumb"))
|
||||||
|
{
|
||||||
|
QCoreApplication app(argc, argv);
|
||||||
|
QStringList files = cmd.positionalArguments();
|
||||||
|
if(files.size() == 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
QString thumb = cmd.value("thumb");
|
||||||
|
int size = 128;
|
||||||
|
bool ok;
|
||||||
|
int size2 = cmd.value("s").toInt(&ok);
|
||||||
|
if(ok)
|
||||||
|
size = size2;
|
||||||
|
|
||||||
|
return generateThumbnail(files.front(), thumb, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSurfaceFormat format;
|
||||||
|
if(useGLES)
|
||||||
|
{
|
||||||
|
format.setMajorVersion(3);
|
||||||
|
format.setMinorVersion(0);
|
||||||
|
format.setRenderableType(QSurfaceFormat::OpenGLES);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
format.setMajorVersion(3);
|
||||||
|
format.setMinorVersion(3);
|
||||||
|
//format.setOption(QSurfaceFormat::DebugContext);
|
||||||
|
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
||||||
|
}
|
||||||
|
QSurfaceFormat::setDefaultFormat(format);
|
||||||
|
|
||||||
|
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
a.setOrganizationName("nou");
|
||||||
|
a.setApplicationName("Tenmon");
|
||||||
|
a.setWindowIcon(QIcon(":/space.nouspiro.tenmon.png"));
|
||||||
|
|
||||||
|
QTranslator translator;
|
||||||
|
QTranslator translator2;
|
||||||
|
QSettings settings;
|
||||||
|
QString lang = settings.value("settings/lang").toString();
|
||||||
|
if(lang.isEmpty())
|
||||||
|
{
|
||||||
|
if(translator.load(QLocale(), "tenmon", "_", ":/translations"))
|
||||||
|
a.installTranslator(&translator);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(translator.load("tenmon_" + lang, ":/translations"))
|
||||||
|
a.installTranslator(&translator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(translator2.load(QLocale(), "tenmon", "_", a.applicationDirPath()))
|
||||||
|
a.installTranslator(&translator2);
|
||||||
|
|
||||||
|
MainWindow w;
|
||||||
|
w.show();
|
||||||
|
|
||||||
|
if(!cmd.positionalArguments().isEmpty() && !cmd.isSet("script"))
|
||||||
|
{
|
||||||
|
QStringList files = cmd.positionalArguments();
|
||||||
|
QStringList paths;
|
||||||
|
for(auto &arg : files)
|
||||||
|
{
|
||||||
|
QUrl url(arg);
|
||||||
|
QFileInfo info(url.isLocalFile() ? url.toLocalFile() : arg);
|
||||||
|
if(info.exists())
|
||||||
|
paths.append(info.canonicalFilePath());
|
||||||
|
}
|
||||||
|
if(paths.size() == 1)
|
||||||
|
w.loadFile(paths.front());
|
||||||
|
else if(paths.size() > 1)
|
||||||
|
w.loadFiles(paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cmd.isSet("script"))
|
||||||
|
{
|
||||||
|
QStringList paths = cmd.positionalArguments();
|
||||||
|
QString script = cmd.value("script");
|
||||||
|
QString outdir = cmd.value("outdir");
|
||||||
|
QString arg = cmd.value("scriptarg");
|
||||||
|
if(!QDir::isAbsolutePath(script))script = QDir::currentPath() + "/" + script;
|
||||||
|
if(!QDir::isAbsolutePath(outdir))outdir = QDir::currentPath() + "/" + outdir;
|
||||||
|
bool noexit = cmd.isSet("noexit");
|
||||||
|
if(!noexit)w.hide();
|
||||||
|
w.runScript(script, outdir, paths, arg, !noexit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.exec();
|
||||||
|
}
|
||||||
@@ -18,10 +18,12 @@
|
|||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QStatusBar>
|
#include <QStatusBar>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
|
#include <QImageWriter>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
#include <QTimer>
|
||||||
#include "loadrunable.h"
|
#include "loadrunable.h"
|
||||||
#include "markedfiles.h"
|
#include "markedfiles.h"
|
||||||
#include "about.h"
|
#include "about.h"
|
||||||
@@ -29,6 +31,7 @@
|
|||||||
#include "settingsdialog.h"
|
#include "settingsdialog.h"
|
||||||
#include "histogram.h"
|
#include "histogram.h"
|
||||||
#include "batchprocessing.h"
|
#include "batchprocessing.h"
|
||||||
|
#include "filemanager.h"
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
@@ -56,16 +59,23 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
for(auto format : supportedFormats)
|
for(auto format : supportedFormats)
|
||||||
{
|
{
|
||||||
QMimeType mimeType = db.mimeTypeForName(format);
|
QMimeType mimeType = db.mimeTypeForName(format);
|
||||||
_saveFilter.append(mimeType.filterString() + ";;");
|
|
||||||
_openFilter.append("*.");
|
_openFilter.append("*.");
|
||||||
_openFilter.append(mimeType.suffixes().join(" *."));
|
_openFilter.append(mimeType.suffixes().join(" *."));
|
||||||
_openFilter.append(" ");
|
_openFilter.append(" ");
|
||||||
nameFilter.append(mimeType.suffixes());
|
nameFilter.append(mimeType.suffixes());
|
||||||
}
|
}
|
||||||
|
auto supportedWrite = QImageWriter::supportedMimeTypes();
|
||||||
|
for(auto format : supportedWrite)
|
||||||
|
{
|
||||||
|
QMimeType mimeType = db.mimeTypeForName(format);
|
||||||
|
_saveFilter.append(mimeType.filterString() + ";;");
|
||||||
|
}
|
||||||
|
|
||||||
_openFilter.append("*.fit *.fits *.fts *.fz *.xisf *.cr2 *.cr3 *.nef *.dng)");
|
_openFilter.append("*.fit *.fits *.fts *.fz *.xisf *.cr2 *.cr3 *.nef *.dng)");
|
||||||
_openFilter.append(tr(";;All files (*)"));
|
_openFilter.append(tr(";;All files (*)"));
|
||||||
nameFilter.append({"fit", "fits", "fts", "fz", "xisf", "cr2", "cr3", "nef", "dng"});
|
nameFilter.append({"fit", "fits", "fts", "fz", "xisf", "cr2", "cr3", "nef", "dng"});
|
||||||
QImageReader::setAllocationLimit(0);
|
QImageReader::setAllocationLimit(0);
|
||||||
|
_openSuffix = {nameFilter.constBegin(), nameFilter.constEnd()};
|
||||||
|
|
||||||
m_info = new ImageInfo(this);
|
m_info = new ImageInfo(this);
|
||||||
QDockWidget *infoDock = new QDockWidget(tr("Image info"), this);
|
QDockWidget *infoDock = new QDockWidget(tr("Image info"), this);
|
||||||
@@ -92,21 +102,25 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
connect(m_stretchPanel, &StretchToolbar::invert, m_image, &ImageScrollArea::invert);
|
connect(m_stretchPanel, &StretchToolbar::invert, m_image, &ImageScrollArea::invert);
|
||||||
connect(m_stretchPanel, &StretchToolbar::superPixel, m_image, &ImageScrollArea::superPixel);
|
connect(m_stretchPanel, &StretchToolbar::superPixel, m_image, &ImageScrollArea::superPixel);
|
||||||
connect(m_stretchPanel, &StretchToolbar::falseColor, m_image, &ImageScrollArea::falseColor);
|
connect(m_stretchPanel, &StretchToolbar::falseColor, m_image, &ImageScrollArea::falseColor);
|
||||||
|
connect(m_stretchPanel, &StretchToolbar::drawGrid, m_image, &ImageScrollArea::drawGrid);
|
||||||
|
|
||||||
m_ringList = new ImageRingList(m_database, nameFilter, this);
|
m_ringList = new ImageRingList(m_database, nameFilter, this);
|
||||||
m_filesystem = new FilesystemWidget(m_ringList, this);
|
m_filesystem = new FilesystemWidget(m_ringList, this);
|
||||||
connect(m_filesystem, SIGNAL(fileSelected(int)), this, SLOT(loadFile(int)));
|
connect(m_filesystem, &FilesystemWidget::fileSelected, this, static_cast<void (MainWindow::*)(int)>(&MainWindow::loadFile));
|
||||||
connect(m_filesystem, &FilesystemWidget::sortChanged, m_ringList, &ImageRingList::setSort);
|
connect(m_filesystem, &FilesystemWidget::sortChanged, m_ringList, &ImageRingList::setSort);
|
||||||
connect(m_filesystem, &FilesystemWidget::reverseSort, m_ringList, &ImageRingList::reverseSort);
|
connect(m_filesystem, &FilesystemWidget::reverseSort, m_ringList, &ImageRingList::reverseSort);
|
||||||
|
|
||||||
|
m_filetree = nullptr;
|
||||||
|
#if !defined(FLATPAK) || !defined(__aarch64__)//bug with QTreeView and QFileSystemModel on ARM64 under flatpak
|
||||||
m_filetree = new Filetree(this);
|
m_filetree = new Filetree(this);
|
||||||
connect(m_filetree, &Filetree::fileSelected, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
|
connect(m_filetree, &Filetree::fileSelected, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
|
||||||
connect(m_filetree, &Filetree::copyFiles, [this](const QString &path){ copyOrMove(true, path); });
|
connect(m_filetree, &Filetree::copyFiles, [this](const QString &path){ copyOrMove(true, path); });
|
||||||
connect(m_filetree, &Filetree::moveFiles, [this](const QString &path){ copyOrMove(false, path); });
|
connect(m_filetree, &Filetree::moveFiles, [this](const QString &path){ copyOrMove(false, path); });
|
||||||
connect(m_filetree, &Filetree::indexDirectory, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::indexDir));
|
connect(m_filetree, &Filetree::indexDirectory, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::indexDir));
|
||||||
|
#endif
|
||||||
|
|
||||||
m_databaseView = new DataBaseView(m_database, this);
|
m_databaseView = new DataBaseView(m_database, this);
|
||||||
connect(m_databaseView, SIGNAL(loadFile(QString)), this, SLOT(loadFile(QString)));
|
connect(m_databaseView, &DataBaseView::loadFile, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
|
||||||
|
|
||||||
#ifdef PLATESOLVER
|
#ifdef PLATESOLVER
|
||||||
_plateSolving = new PlateSolving(this);
|
_plateSolving = new PlateSolving(this);
|
||||||
@@ -114,6 +128,23 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
_plateSolving->hide();
|
_plateSolving->hide();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
_databaseTree = new DatabaseTree(m_database, this);
|
||||||
|
|
||||||
|
QToolBar *navigationToolbar = new QToolBar(tr("Navigation toolbar"), this);
|
||||||
|
navigationToolbar->setObjectName("navigationtoolbar");
|
||||||
|
navigationToolbar->hide();
|
||||||
|
QAction *prevAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowLeft), tr("Previous image"));
|
||||||
|
prevAction->setShortcuts({Qt::Key_Left, Qt::Key_Up});
|
||||||
|
QAction *nextAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowRight), tr("Next image"));
|
||||||
|
nextAction->setShortcuts({Qt::Key_Right, Qt::Key_Down});
|
||||||
|
QAction *prevSubAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowUp), tr("Prev sub image"), Qt::Key_PageUp);
|
||||||
|
QAction *nextSubAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowDown), tr("Next sub image"), Qt::Key_PageDown);
|
||||||
|
connect(prevAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::decrement));
|
||||||
|
connect(nextAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::increment));
|
||||||
|
connect(prevSubAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::prevSubImage));
|
||||||
|
connect(nextSubAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::nextSubImage));
|
||||||
|
|
||||||
|
addToolBar(Qt::TopToolBarArea, navigationToolbar);
|
||||||
addToolBar(Qt::TopToolBarArea, m_stretchPanel);
|
addToolBar(Qt::TopToolBarArea, m_stretchPanel);
|
||||||
|
|
||||||
QDockWidget *filesystemDock = new QDockWidget(tr("Filesystem"), this);
|
QDockWidget *filesystemDock = new QDockWidget(tr("Filesystem"), this);
|
||||||
@@ -125,13 +156,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
databaseViewDock->setWidget(m_databaseView);
|
databaseViewDock->setWidget(m_databaseView);
|
||||||
databaseViewDock->setObjectName("databaseViewDock");
|
databaseViewDock->setObjectName("databaseViewDock");
|
||||||
databaseViewDock->hide();
|
databaseViewDock->hide();
|
||||||
|
connect(databaseViewDock, &QDockWidget::visibilityChanged, m_databaseView, &DataBaseView::visible);
|
||||||
addDockWidget(Qt::BottomDockWidgetArea, databaseViewDock);
|
addDockWidget(Qt::BottomDockWidgetArea, databaseViewDock);
|
||||||
|
|
||||||
QDockWidget *filetreeDock = new QDockWidget(tr("File tree"), this);
|
QDockWidget *filetreeDock = nullptr;
|
||||||
|
#if !defined(FLATPAK) || !defined(__aarch64__)
|
||||||
|
filetreeDock = new QDockWidget(tr("File tree"), this);
|
||||||
filetreeDock->setWidget(m_filetree);
|
filetreeDock->setWidget(m_filetree);
|
||||||
filetreeDock->setObjectName("filetreeDock");
|
filetreeDock->setObjectName("filetreeDock");
|
||||||
databaseViewDock->hide();
|
databaseViewDock->hide();
|
||||||
addDockWidget(Qt::LeftDockWidgetArea, filetreeDock);
|
addDockWidget(Qt::LeftDockWidgetArea, filetreeDock);
|
||||||
|
#endif
|
||||||
|
|
||||||
Histogram *histogram = new Histogram(this);
|
Histogram *histogram = new Histogram(this);
|
||||||
QDockWidget *histogramDock = new QDockWidget(tr("Histogram"), this);
|
QDockWidget *histogramDock = new QDockWidget(tr("Histogram"), this);
|
||||||
@@ -140,6 +175,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
histogramDock->hide();
|
histogramDock->hide();
|
||||||
addDockWidget(Qt::LeftDockWidgetArea, histogramDock);
|
addDockWidget(Qt::LeftDockWidgetArea, histogramDock);
|
||||||
|
|
||||||
|
DatabaseTreeView *databaseTreeView = new DatabaseTreeView(m_database, this);
|
||||||
|
QDockWidget *databaseTreeDock = new QDockWidget(tr("Database Tree"), this);
|
||||||
|
databaseTreeDock->setObjectName("databasetreeDock");
|
||||||
|
databaseTreeDock->setWidget(databaseTreeView);
|
||||||
|
databaseTreeDock->hide();
|
||||||
|
connect(databaseTreeDock, &QDockWidget::visibilityChanged, databaseTreeView, &DatabaseTreeView::visible);
|
||||||
|
connect(databaseTreeView, &DatabaseTreeView::loadFile, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
|
||||||
|
addDockWidget(Qt::BottomDockWidgetArea, databaseTreeDock);
|
||||||
|
|
||||||
setWindowTitle(tr("Tenmon"));
|
setWindowTitle(tr("Tenmon"));
|
||||||
|
|
||||||
connect(m_ringList, &ImageRingList::pixmapLoaded, m_image, &ImageScrollArea::imageLoaded);
|
connect(m_ringList, &ImageRingList::pixmapLoaded, m_image, &ImageScrollArea::imageLoaded);
|
||||||
@@ -151,30 +195,39 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
connect(m_ringList, &ImageRingList::pixmapLoaded, histogram, &Histogram::imageLoaded);
|
connect(m_ringList, &ImageRingList::pixmapLoaded, histogram, &Histogram::imageLoaded);
|
||||||
#ifdef PLATESOLVER
|
#ifdef PLATESOLVER
|
||||||
connect(m_ringList, &ImageRingList::pixmapLoaded, _plateSolving, &PlateSolving::imageLoaded);
|
connect(m_ringList, &ImageRingList::pixmapLoaded, _plateSolving, &PlateSolving::imageLoaded);
|
||||||
|
connect(_plateSolving, &PlateSolving::headerUpdated, m_ringList, &ImageRingList::reloadImage);
|
||||||
#endif
|
#endif
|
||||||
connect(m_image, &ImageScrollArea::fileDropped, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
|
connect(m_image, &ImageScrollArea::fileDropped, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
|
||||||
|
|
||||||
QMenu *fileMenu = new QMenu(tr("File"), this);
|
QMenu *fileMenu = new QMenu(tr("File"), this);
|
||||||
fileMenu->addAction(tr("Open"), QKeySequence::Open, this, SLOT(loadFile()));
|
fileMenu->addAction(tr("Open"), QKeySequence::Open, this, static_cast<void (MainWindow::*)()>(&MainWindow::loadFile));
|
||||||
fileMenu->addAction(tr("Open directory recursively"), this, &MainWindow::loadDir);
|
fileMenu->addAction(tr("Open directory recursively"), this, &MainWindow::loadDir);
|
||||||
QAction *saveAs = fileMenu->addAction(tr("Save as"), QKeySequence::Save, this, SLOT(saveAs()));
|
QAction *saveAs = fileMenu->addAction(tr("Save as"), QKeySequence::Save, this, &MainWindow::saveAs);
|
||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
fileMenu->addAction(tr("Copy marked files"), Qt::Key_F5, this, SLOT(copyMarked()));
|
#if !defined(FLATPAK) || !defined(__aarch64__)
|
||||||
fileMenu->addAction(tr("Move marked files"), Qt::Key_F6, this, SLOT(moveMarked()));
|
fileMenu->addAction(tr("File manager"), this, &MainWindow::openFileManager);
|
||||||
fileMenu->addAction(tr("Move marked files to trash"), QKeySequence::Delete, this, &MainWindow::deleteMarked);
|
#endif
|
||||||
|
fileMenu->addAction(tr("Copy marked files"), Qt::Key_F5, this, &MainWindow::copyMarked);
|
||||||
|
fileMenu->addAction(tr("Move marked files"), Qt::Key_F6, this, &MainWindow::moveMarked);
|
||||||
|
QAction *deleteAction = fileMenu->addAction(tr("Move marked files to trash"), QKeySequence::Delete, this, &MainWindow::deleteMarked);
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
deleteAction->setShortcuts(QList<QKeySequence>({Qt::Key_Backspace, QKeySequence::Delete}));
|
||||||
|
#else
|
||||||
|
deleteAction->setShortcuts(QKeySequence::Delete);
|
||||||
|
#endif
|
||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
fileMenu->addAction(tr("Index directory"), this, SLOT(indexDir()));
|
fileMenu->addAction(tr("Index directory"), this, static_cast<void (MainWindow::*)()>(&MainWindow::indexDir));
|
||||||
fileMenu->addAction(tr("Reindex files"), this, SLOT(reindex()));
|
fileMenu->addAction(tr("Reindex files"), this, &MainWindow::reindex);
|
||||||
fileMenu->addAction(tr("Export database to CSV"), this, &MainWindow::exportCSV);
|
fileMenu->addAction(tr("Export database to CSV"), this, &MainWindow::exportCSV);
|
||||||
fileMenu->addAction(tr("Batch processing"), Qt::Key_B | Qt::CTRL, [this](){
|
fileMenu->addAction(tr("Batch processing"), Qt::Key_B | Qt::CTRL, [this](){
|
||||||
BatchProcessing *batchProcessing = new BatchProcessing(this);
|
BatchProcessing *batchProcessing = new BatchProcessing(m_database, this);
|
||||||
batchProcessing->exec();
|
batchProcessing->exec();
|
||||||
delete batchProcessing;
|
delete batchProcessing;
|
||||||
});
|
});
|
||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, SLOT(liveMode(bool)));
|
QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, &MainWindow::liveMode);
|
||||||
liveModeAction->setCheckable(true);
|
liveModeAction->setCheckable(true);
|
||||||
QAction *exitAction = fileMenu->addAction(tr("Exit"), this, SLOT(close()));
|
QAction *exitAction = fileMenu->addAction(tr("Exit"), QCoreApplication::instance(), &QCoreApplication::quit, Qt::QueuedConnection);
|
||||||
exitAction->setShortcut(QKeySequence::Quit);
|
exitAction->setShortcut(QKeySequence::Quit);
|
||||||
menuBar()->addMenu(fileMenu);
|
menuBar()->addMenu(fileMenu);
|
||||||
|
|
||||||
@@ -182,11 +235,18 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
editMenu->addAction(tr("Settings"), this, &MainWindow::showSettingsDialog);
|
editMenu->addAction(tr("Settings"), this, &MainWindow::showSettingsDialog);
|
||||||
menuBar()->addMenu(editMenu);
|
menuBar()->addMenu(editMenu);
|
||||||
|
|
||||||
|
QMenu *navigationMenu = new QMenu(tr("Navigation"), this);
|
||||||
|
navigationMenu->addAction(prevAction);
|
||||||
|
navigationMenu->addAction(nextAction);
|
||||||
|
navigationMenu->addAction(prevSubAction);
|
||||||
|
navigationMenu->addAction(nextSubAction);
|
||||||
|
menuBar()->addMenu(navigationMenu);
|
||||||
|
|
||||||
QMenu *viewMenu = new QMenu(tr("View"), this);
|
QMenu *viewMenu = new QMenu(tr("View"), this);
|
||||||
viewMenu->addAction(tr("Zoom In"), QKeySequence::ZoomIn, m_image, &ImageScrollArea::zoomIn);
|
viewMenu->addAction(tr("Zoom In"), QKeySequence::ZoomIn, m_image, &ImageScrollArea::zoomIn);
|
||||||
viewMenu->addAction(tr("Zoom Out"), QKeySequence::ZoomOut, m_image, &ImageScrollArea::zoomOut);
|
viewMenu->addAction(tr("Zoom Out"), QKeySequence::ZoomOut, m_image, &ImageScrollArea::zoomOut);
|
||||||
viewMenu->addAction(tr("Best Fit"), QKeySequence("Ctrl+1"), m_image, &ImageScrollArea::bestFit);
|
viewMenu->addAction(tr("Best Fit"), QKeySequence("Ctrl+1"), m_image, &ImageScrollArea::bestFit);
|
||||||
viewMenu->addAction(tr("100%"), m_image, &ImageScrollArea::oneToOne);
|
viewMenu->addAction(tr("100%"), QKeySequence("Ctrl+0"), m_image, &ImageScrollArea::oneToOne);
|
||||||
viewMenu->addSeparator();
|
viewMenu->addSeparator();
|
||||||
QMenu *bayerMenu = viewMenu->addMenu(tr("Bayer mask"));
|
QMenu *bayerMenu = viewMenu->addMenu(tr("Bayer mask"));
|
||||||
QActionGroup *bayerActionGroup = new QActionGroup(this);
|
QActionGroup *bayerActionGroup = new QActionGroup(this);
|
||||||
@@ -207,6 +267,28 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
settings.setValue("mainwindow/bayermask", data);
|
settings.setValue("mainwindow/bayermask", data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QStringList colormaps = {"Autumn", "Bone", "Jet", "Winter", "Rainbow", "Ocean", "Summer", "Spring", "Cool", "HSV", "Pink", "Hot", "Parula", "Magma",
|
||||||
|
"Inferno", "Plasma", "Viridis", "Cividis", "Twilight", "Twilight shifted", "Turbo", "Deepgreen"};
|
||||||
|
QMenu *colormapMenu = viewMenu->addMenu(tr("Colormap"));
|
||||||
|
QActionGroup *colormapActionGroup = new QActionGroup(this);
|
||||||
|
|
||||||
|
QImage cmImg = ImageWidget::loadColormap();
|
||||||
|
for(int i=0; i<cmImg.height(); i++)
|
||||||
|
{
|
||||||
|
QImage icon = cmImg.copy(0, i, cmImg.width(), 1).scaled(32, 32);
|
||||||
|
QAction *action = colormapActionGroup->addAction(i < colormaps.size() ? colormaps[i] : tr("User %1").arg(i - colormaps.size() + 1));
|
||||||
|
action->setIcon(QPixmap::fromImage(icon));
|
||||||
|
action->setCheckable(true); action->setData(i);
|
||||||
|
colormapMenu->addAction(action);
|
||||||
|
}
|
||||||
|
viewMenu->addMenu(colormapMenu);
|
||||||
|
connect(colormapActionGroup, &QActionGroup::triggered, [this](QAction *action){
|
||||||
|
int data = action->data().toInt();
|
||||||
|
m_image->setColormap(data);
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue("mainwindow/colormap", data);
|
||||||
|
});
|
||||||
|
|
||||||
QAction *thumbnailsAction = viewMenu->addAction(tr("Thumbnails"), Qt::Key_F2, [this](bool checked){
|
QAction *thumbnailsAction = viewMenu->addAction(tr("Thumbnails"), Qt::Key_F2, [this](bool checked){
|
||||||
if(SettingsDialog::loadThumbsizes())m_ringList->clearThumbnails();
|
if(SettingsDialog::loadThumbsizes())m_ringList->clearThumbnails();
|
||||||
m_image->allocateThumbnails(m_ringList->imageNames());
|
m_image->allocateThumbnails(m_ringList->imageNames());
|
||||||
@@ -217,14 +299,18 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
thumbnailsAction->setCheckable(true);
|
thumbnailsAction->setCheckable(true);
|
||||||
QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), Qt::Key_F3, m_ringList, &ImageRingList::toggleSlideshow);
|
QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), Qt::Key_F3, m_ringList, &ImageRingList::toggleSlideshow);
|
||||||
slideshowAction->setCheckable(true);
|
slideshowAction->setCheckable(true);
|
||||||
|
viewMenu->addSeparator();
|
||||||
|
auto actionList = m_stretchPanel->actions();
|
||||||
|
actionList.removeFirst();
|
||||||
|
viewMenu->addActions(actionList);
|
||||||
menuBar()->addMenu(viewMenu);
|
menuBar()->addMenu(viewMenu);
|
||||||
|
|
||||||
QMenu *selectMenu = new QMenu(tr("Select"), this);
|
QMenu *selectMenu = new QMenu(tr("Select"), this);
|
||||||
selectMenu->addAction(tr("Mark"), Qt::Key_F7, this, SLOT(markImage()));
|
selectMenu->addAction(tr("Mark"), Qt::Key_F7, this, &MainWindow::markImage);
|
||||||
selectMenu->addAction(tr("Unmark"), Qt::Key_F8, this, SLOT(unmarkImage()));
|
selectMenu->addAction(tr("Unmark"), Qt::Key_F8, this, &MainWindow::unmarkImage);
|
||||||
selectMenu->addSeparator();
|
selectMenu->addSeparator();
|
||||||
selectMenu->addAction(tr("Mark and next"), Qt::Key_M, this, SLOT(markAndNext()));
|
selectMenu->addAction(tr("Mark and next"), Qt::Key_M, this, &MainWindow::markAndNext);
|
||||||
selectMenu->addAction(tr("Unmark and next"), Qt::Key_X, this, SLOT(unmarkAndNext()));
|
selectMenu->addAction(tr("Unmark and next"), Qt::Key_X, this, &MainWindow::unmarkAndNext);
|
||||||
selectMenu->addSeparator();
|
selectMenu->addSeparator();
|
||||||
selectMenu->addAction(tr("Show marked list"), this, &MainWindow::showMarkFilesDialog);
|
selectMenu->addAction(tr("Show marked list"), this, &MainWindow::showMarkFilesDialog);
|
||||||
QAction *openMarked = selectMenu->addAction(tr("Open marked"), m_ringList, &ImageRingList::setMarked);
|
QAction *openMarked = selectMenu->addAction(tr("Open marked"), m_ringList, &ImageRingList::setMarked);
|
||||||
@@ -259,9 +345,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
QMenu *dockMenu = new QMenu(tr("Docks"), this);
|
QMenu *dockMenu = new QMenu(tr("Docks"), this);
|
||||||
dockMenu->addAction(infoDock->toggleViewAction());
|
dockMenu->addAction(infoDock->toggleViewAction());
|
||||||
dockMenu->addAction(m_stretchPanel->toggleViewAction());
|
dockMenu->addAction(m_stretchPanel->toggleViewAction());
|
||||||
|
dockMenu->addAction(navigationToolbar->toggleViewAction());
|
||||||
dockMenu->addAction(filesystemDock->toggleViewAction());
|
dockMenu->addAction(filesystemDock->toggleViewAction());
|
||||||
dockMenu->addAction(databaseViewDock->toggleViewAction());
|
dockMenu->addAction(databaseViewDock->toggleViewAction());
|
||||||
dockMenu->addAction(filetreeDock->toggleViewAction());
|
dockMenu->addAction(databaseTreeDock->toggleViewAction());
|
||||||
|
if(filetreeDock)dockMenu->addAction(filetreeDock->toggleViewAction());
|
||||||
dockMenu->addAction(histogramDock->toggleViewAction());
|
dockMenu->addAction(histogramDock->toggleViewAction());
|
||||||
#ifdef PLATESOLVER
|
#ifdef PLATESOLVER
|
||||||
dockMenu->addAction(_plateSolving->toggleViewAction());
|
dockMenu->addAction(_plateSolving->toggleViewAction());
|
||||||
@@ -269,7 +357,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
menuBar()->addMenu(dockMenu);
|
menuBar()->addMenu(dockMenu);
|
||||||
|
|
||||||
QMenu *helpMenu = menuBar()->addMenu(tr("Help"));
|
QMenu *helpMenu = menuBar()->addMenu(tr("Help"));
|
||||||
helpMenu->addAction(tr("Help"), QKeySequence::HelpContents, [this]{ HelpDialog help(this); help.exec(); });
|
helpMenu->addAction(tr("Help"), QKeySequence::HelpContents, [this]{ HelpDialog *help = new HelpDialog(this); help->show(); });
|
||||||
helpMenu->addAction(tr("About Tenmon"), [this]{ About about(this); about.exec(); });
|
helpMenu->addAction(tr("About Tenmon"), [this]{ About about(this); about.exec(); });
|
||||||
helpMenu->addAction(tr("About Qt"), [this](){ QMessageBox::aboutQt(this); });
|
helpMenu->addAction(tr("About Qt"), [this](){ QMessageBox::aboutQt(this); });
|
||||||
helpMenu->addAction(tr("Check for update"), this, &MainWindow::checkNewVersion);
|
helpMenu->addAction(tr("Check for update"), this, &MainWindow::checkNewVersion);
|
||||||
@@ -291,6 +379,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
case 3:
|
case 3:
|
||||||
bggrAction->setChecked(true); break;
|
bggrAction->setChecked(true); break;
|
||||||
}
|
}
|
||||||
|
int colormap = settings.value("mainwindow/colormap", 4).toInt();
|
||||||
|
if(colormap >= 0 && colormap < colormapActionGroup->actions().size())
|
||||||
|
colormapActionGroup->actions().at(colormap)->setChecked(true);
|
||||||
|
|
||||||
|
m_image->setBayerMask(bayermask);
|
||||||
|
m_image->setColormap(colormap);
|
||||||
|
|
||||||
QStringList standardLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
|
QStringList standardLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
|
||||||
if(standardLocations.size())
|
if(standardLocations.size())
|
||||||
@@ -298,22 +392,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
|
|
||||||
_lastDir = settings.value("mainwindow/lastdir", _lastDir).toString();
|
_lastDir = settings.value("mainwindow/lastdir", _lastDir).toString();
|
||||||
|
|
||||||
QStringList args = QCoreApplication::arguments();
|
|
||||||
args.removeFirst();
|
|
||||||
for(auto &arg : args)
|
|
||||||
{
|
|
||||||
QUrl url(arg);
|
|
||||||
QFileInfo info(url.isLocalFile() ? url.toLocalFile() : arg);
|
|
||||||
if(info.exists())
|
|
||||||
{
|
|
||||||
m_ringList->setFile(info.canonicalFilePath());
|
|
||||||
updateWindowTitle();
|
|
||||||
_lastDir = info.absoluteDir().absolutePath();
|
|
||||||
settings.setValue("mainwindow/lastdir", _lastDir);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_image->setFocus();
|
m_image->setFocus();
|
||||||
|
|
||||||
// workaround for nasty wayland backend bug https://bugreports.qt.io/browse/QTBUG-87332
|
// workaround for nasty wayland backend bug https://bugreports.qt.io/browse/QTBUG-87332
|
||||||
@@ -322,7 +400,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
infoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
infoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
||||||
filesystemDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
filesystemDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
||||||
databaseViewDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
databaseViewDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
||||||
filetreeDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
if(filetreeDock)filetreeDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
||||||
|
if(_plateSolving)_plateSolving->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
||||||
m_stretchPanel->setFloatable(false);
|
m_stretchPanel->setFloatable(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,32 +411,6 @@ MainWindow::~MainWindow()
|
|||||||
delete m_database;
|
delete m_database;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::keyPressEvent(QKeyEvent *event)
|
|
||||||
{
|
|
||||||
switch (event->key())
|
|
||||||
{
|
|
||||||
case Qt::Key_Left:
|
|
||||||
case Qt::Key_Up:
|
|
||||||
m_ringList->decrement();
|
|
||||||
break;
|
|
||||||
case Qt::Key_Right:
|
|
||||||
case Qt::Key_Down:
|
|
||||||
m_ringList->increment();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
event->ignore();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(event->isAccepted())
|
|
||||||
updateWindowTitle();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::keyReleaseEvent(QKeyEvent *event)
|
|
||||||
{
|
|
||||||
event->ignore();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::setupSigterm()
|
void MainWindow::setupSigterm()
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
@@ -374,7 +427,7 @@ void MainWindow::setupSigterm()
|
|||||||
|
|
||||||
::socketpair(AF_UNIX, SOCK_STREAM, 0, socketPair);
|
::socketpair(AF_UNIX, SOCK_STREAM, 0, socketPair);
|
||||||
socketNotifier = new QSocketNotifier(socketPair[1], QSocketNotifier::Read, this);
|
socketNotifier = new QSocketNotifier(socketPair[1], QSocketNotifier::Read, this);
|
||||||
connect(socketNotifier, SIGNAL(activated(int)), this, SLOT(socketNotify()));
|
connect(socketNotifier, &QSocketNotifier::activated, this, &MainWindow::socketNotify);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,9 +584,16 @@ void MainWindow::loadFile(const QString &path)
|
|||||||
_lastDir = info.canonicalPath();
|
_lastDir = info.canonicalPath();
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.setValue("mainwindow/lastdir", _lastDir);
|
settings.setValue("mainwindow/lastdir", _lastDir);
|
||||||
|
if(settings.value("settings/bestfit", false).toBool())
|
||||||
|
m_image->bestFit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::loadFiles(const QStringList &paths)
|
||||||
|
{
|
||||||
|
m_ringList->setFiles(paths);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::loadFile(int row)
|
void MainWindow::loadFile(int row)
|
||||||
{
|
{
|
||||||
m_ringList->loadFile(row);
|
m_ringList->loadFile(row);
|
||||||
@@ -579,12 +639,16 @@ void MainWindow::reindex()
|
|||||||
void MainWindow::saveAs()
|
void MainWindow::saveAs()
|
||||||
{
|
{
|
||||||
QString selectedFilter;
|
QString selectedFilter;
|
||||||
|
ImagePtr ptr = m_ringList->currentImage();
|
||||||
|
if(!ptr)return;
|
||||||
|
|
||||||
|
QFileInfo srcFile(ptr->name());
|
||||||
QString file = QFileDialog::getSaveFileName(this,
|
QString file = QFileDialog::getSaveFileName(this,
|
||||||
tr("Save as"),
|
tr("Save as"),
|
||||||
_lastDir,
|
_lastDir + "/" + srcFile.baseName(),
|
||||||
_saveFilter,
|
_saveFilter,
|
||||||
&selectedFilter);
|
&selectedFilter);
|
||||||
auto filterToFormat = [](const QString &file, const QString &filter) -> const char*
|
auto filterToFormat = [](const QString &file, const QString &filter) -> const QString
|
||||||
{
|
{
|
||||||
QString suffix = QFileInfo(file).suffix();
|
QString suffix = QFileInfo(file).suffix();
|
||||||
if(!suffix.compare("jpg", Qt::CaseInsensitive) || !suffix.compare("jpeg", Qt::CaseInsensitive))return "jpeg";
|
if(!suffix.compare("jpg", Qt::CaseInsensitive) || !suffix.compare("jpeg", Qt::CaseInsensitive))return "jpeg";
|
||||||
@@ -594,30 +658,31 @@ void MainWindow::saveAs()
|
|||||||
if(filter.contains("png"))return "png";
|
if(filter.contains("png"))return "png";
|
||||||
if(filter.contains("fits"))return "fits";
|
if(filter.contains("fits"))return "fits";
|
||||||
if(filter.contains("xisf"))return "xisf";
|
if(filter.contains("xisf"))return "xisf";
|
||||||
|
QRegularExpression suf("\\(\\*\\.([a-zA-Z]+).*\\)");
|
||||||
|
auto match = suf.match(filter);
|
||||||
|
if(match.hasMatch())
|
||||||
|
return match.captured(1);
|
||||||
return "jpeg";
|
return "jpeg";
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!file.isEmpty())
|
if(!file.isEmpty())
|
||||||
{
|
{
|
||||||
|
auto button = QMessageBox::question(this, tr("Apply stretch?"), tr("Apply current stretch function to image?"));
|
||||||
|
|
||||||
QString format = filterToFormat(file, selectedFilter);
|
QString format = filterToFormat(file, selectedFilter);
|
||||||
|
|
||||||
if(format == "fits" || format == "xisf")
|
convert(file, format, button == QMessageBox::Yes);
|
||||||
{
|
|
||||||
convert(file, format);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
QImage img = m_image->renderToImage();
|
|
||||||
if(!img.isNull())
|
|
||||||
img.save(file, filterToFormat(file, selectedFilter));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::convert(const QString &outfile, const QString &format)
|
void MainWindow::convert(const QString &outfile, const QString &format, bool stretch)
|
||||||
{
|
{
|
||||||
QString file = m_ringList->currentImage()->name();
|
QString file = m_ringList->currentImage()->name();
|
||||||
QThreadPool::globalInstance()->start(new ConvertRunable(file, outfile, format));
|
ConvertRunable::ConvertParams param;
|
||||||
|
param.stretch = stretch;
|
||||||
|
param.mtf = m_stretchPanel->params();
|
||||||
|
|
||||||
|
QThreadPool::globalInstance()->start(new ConvertRunable(file, outfile, format, param));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::markImage()
|
void MainWindow::markImage()
|
||||||
@@ -770,7 +835,7 @@ void MainWindow::checkNewVersion()
|
|||||||
if(QMessageBox::question(this, tr("Update check"), tr("New version %1 is available. Do you want to download it now?").arg(tag)) == QMessageBox::Yes)
|
if(QMessageBox::question(this, tr("Update check"), tr("New version %1 is available. Do you want to download it now?").arg(tag)) == QMessageBox::Yes)
|
||||||
{
|
{
|
||||||
QUrl url(json.object().value("html_url").toString());
|
QUrl url(json.object().value("html_url").toString());
|
||||||
qDebug() << url;
|
qDebug() << "Opening url" << url;
|
||||||
if(url.host() == "gitea.nouspiro.space")
|
if(url.host() == "gitea.nouspiro.space")
|
||||||
QDesktopServices::openUrl(url);
|
QDesktopServices::openUrl(url);
|
||||||
}
|
}
|
||||||
@@ -786,13 +851,38 @@ void MainWindow::checkNewVersion()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::openFileManager()
|
||||||
|
{
|
||||||
|
#if !defined(FLATPAK) || !defined(__aarch64__)
|
||||||
|
FileManager *filemanager = new FileManager(_openSuffix);
|
||||||
|
connect(filemanager, &FileManager::openFile, this, static_cast<void (MainWindow::*)(const QString&)>(&MainWindow::loadFile));
|
||||||
|
filemanager->show();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::runScript(const QString &script, const QString &outdir, const QStringList &paths, const QString &arg, bool exit)
|
||||||
|
{
|
||||||
|
BatchProcessing *batchProcessing = new BatchProcessing(m_database, this);
|
||||||
|
batchProcessing->setOutputDir(outdir);
|
||||||
|
batchProcessing->setPaths(paths);
|
||||||
|
if(exit)batchProcessing->hide();
|
||||||
|
QTimer::singleShot(500, [batchProcessing, script, exit, arg](){
|
||||||
|
batchProcessing->runScript(script, arg, exit);
|
||||||
|
batchProcessing->exec();
|
||||||
|
delete batchProcessing;
|
||||||
|
if(exit)QCoreApplication::exit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::updateWindowTitle()
|
void MainWindow::updateWindowTitle()
|
||||||
{
|
{
|
||||||
ImagePtr ptr = m_ringList->currentImage();
|
ImagePtr ptr = m_ringList->currentImage();
|
||||||
if(ptr)
|
if(ptr)
|
||||||
{
|
{
|
||||||
QFileInfo info(ptr->name());
|
QDir dir(m_ringList->currentDir());
|
||||||
QString title = info.fileName();
|
QString title = dir.relativeFilePath(ptr->name());
|
||||||
|
if(ptr->info().num > 1)
|
||||||
|
title += QString(" [%1/%2]").arg(ptr->info().index + 1).arg(ptr->info().num);
|
||||||
if(m_database->isMarked(ptr->name()))
|
if(m_database->isMarked(ptr->name()))
|
||||||
title += " *";
|
title += " *";
|
||||||
setWindowTitle(title);
|
setWindowTitle(title);
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "stretchtoolbar.h"
|
#include "stretchtoolbar.h"
|
||||||
#include "databaseview.h"
|
#include "databaseview.h"
|
||||||
#include "platesolving.h"
|
#include "platesolving.h"
|
||||||
|
#include "databasetree.h"
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
@@ -23,36 +24,37 @@ class MainWindow : public QMainWindow
|
|||||||
FilesystemWidget *m_filesystem;
|
FilesystemWidget *m_filesystem;
|
||||||
Filetree *m_filetree;
|
Filetree *m_filetree;
|
||||||
DataBaseView *m_databaseView;
|
DataBaseView *m_databaseView;
|
||||||
PlateSolving *_plateSolving;
|
PlateSolving *_plateSolving = nullptr;
|
||||||
|
DatabaseTree *_databaseTree = nullptr;
|
||||||
static int socketPair[2];
|
static int socketPair[2];
|
||||||
QSocketNotifier *socketNotifier;
|
QSocketNotifier *socketNotifier;
|
||||||
QString _lastDir;
|
QString _lastDir;
|
||||||
bool _maximized;
|
bool _maximized;
|
||||||
QString _openFilter;
|
QString _openFilter;
|
||||||
QString _saveFilter;
|
QString _saveFilter;
|
||||||
|
QSet<QString> _openSuffix;
|
||||||
public:
|
public:
|
||||||
MainWindow(QWidget *parent = 0);
|
MainWindow(QWidget *parent = 0);
|
||||||
~MainWindow() override;
|
~MainWindow() override;
|
||||||
protected:
|
protected:
|
||||||
void keyPressEvent(QKeyEvent *event) override;
|
|
||||||
void keyReleaseEvent(QKeyEvent *event) override;
|
|
||||||
void setupSigterm();
|
void setupSigterm();
|
||||||
static void signalHandler(int);
|
static void signalHandler(int);
|
||||||
void closeEvent(QCloseEvent *event) override;
|
void closeEvent(QCloseEvent *event) override;
|
||||||
void copyOrMove(bool copy);
|
void copyOrMove(bool copy);
|
||||||
void copyOrMove(bool copy, const QString &dest);
|
void copyOrMove(bool copy, const QString &dest);
|
||||||
protected slots:
|
public slots:
|
||||||
void socketNotify();
|
void socketNotify();
|
||||||
void updateWindowTitle();
|
void updateWindowTitle();
|
||||||
void loadFile();
|
void loadFile();
|
||||||
void loadFile(const QString &path);
|
void loadFile(const QString &path);
|
||||||
|
void loadFiles(const QStringList &paths);
|
||||||
void loadFile(int row);
|
void loadFile(int row);
|
||||||
void loadDir();
|
void loadDir();
|
||||||
void indexDir();
|
void indexDir();
|
||||||
void indexDir(const QString &dir);
|
void indexDir(const QString &dir);
|
||||||
void reindex();
|
void reindex();
|
||||||
void saveAs();
|
void saveAs();
|
||||||
void convert(const QString &outfile, const QString &format);
|
void convert(const QString &outfile, const QString &format, bool stretch);
|
||||||
void markImage();
|
void markImage();
|
||||||
void unmarkImage();
|
void unmarkImage();
|
||||||
void markAndNext();
|
void markAndNext();
|
||||||
@@ -68,6 +70,8 @@ protected slots:
|
|||||||
void showSettingsDialog();
|
void showSettingsDialog();
|
||||||
void exportCSV();
|
void exportCSV();
|
||||||
void checkNewVersion();
|
void checkNewVersion();
|
||||||
|
void openFileManager();
|
||||||
|
void runScript(const QString &script, const QString &outdir, const QStringList &paths, const QString &arg, bool exit);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef MTFPARAM_H
|
||||||
|
#define MTFPARAM_H
|
||||||
|
|
||||||
|
struct MTFParam
|
||||||
|
{
|
||||||
|
float blackPoint[3] = {0.0f, 0.0f, 0.0f};
|
||||||
|
float midPoint[3] = {0.5f, 0.5f, 0.5f};
|
||||||
|
float whitePoint[3] = {1.0f, 1.0f, 1.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MTFPARAM_H
|
||||||
@@ -44,6 +44,7 @@ PlateSolving::PlateSolving(QWidget *parent)
|
|||||||
connect(_solver, &Solver::solvingDone, this, &PlateSolving::solvingDone);
|
connect(_solver, &Solver::solvingDone, this, &PlateSolving::solvingDone);
|
||||||
connect(_solver, &Solver::extractionDone, this, &PlateSolving::extractionDone);
|
connect(_solver, &Solver::extractionDone, this, &PlateSolving::extractionDone);
|
||||||
connect(_solver, &Solver::logOutput, [this](const QString &log){ _ui->log->appendPlainText(log); });
|
connect(_solver, &Solver::logOutput, [this](const QString &log){ _ui->log->appendPlainText(log); });
|
||||||
|
connect(_solver, &Solver::headerUpdated, this, &PlateSolving::headerUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlateSolving::~PlateSolving()
|
PlateSolving::~PlateSolving()
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef PLATESOLVING_H
|
#ifndef PLATESOLVING_H
|
||||||
#define PLATESOLVING_H
|
#define PLATESOLVING_H
|
||||||
|
|
||||||
#include "qelapsedtimer.h"
|
#include <QElapsedTimer>
|
||||||
#include <QDockWidget>
|
#include <QDockWidget>
|
||||||
|
|
||||||
class Solver;
|
class Solver;
|
||||||
@@ -32,6 +32,8 @@ public slots:
|
|||||||
void updateHeader();
|
void updateHeader();
|
||||||
void imageLoaded(Image *image);
|
void imageLoaded(Image *image);
|
||||||
void settings();
|
void settings();
|
||||||
|
signals:
|
||||||
|
void headerUpdated(const QString &path);
|
||||||
private:
|
private:
|
||||||
Ui::PlateSolving *_ui;
|
Ui::PlateSolving *_ui;
|
||||||
};
|
};
|
||||||
+289
-19
@@ -1,12 +1,24 @@
|
|||||||
#include "rawimage.h"
|
#include "rawimage.h"
|
||||||
#include <QDebug>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
#ifndef NO_QT
|
||||||
|
#include <lcms2.h>
|
||||||
|
#include <QDebug>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QFloat16>
|
#include <QFloat16>
|
||||||
#include <QColorSpace>
|
#include <QColorSpace>
|
||||||
#include <lcms2.h>
|
|
||||||
|
|
||||||
using F16 = qfloat16;
|
using F16 = qfloat16;
|
||||||
|
#else
|
||||||
|
#define __STDC_WANT_IEC_60559_TYPES_EXT__
|
||||||
|
#include <float.h>
|
||||||
|
#ifdef FLT16_MAX
|
||||||
|
using F16 = _Float16;
|
||||||
|
#else
|
||||||
|
#include "tfloat16.h"
|
||||||
|
using F16 = TFloat16;// this is only for MXE
|
||||||
|
#endif // FLT16_MAX
|
||||||
|
|
||||||
|
#endif // NO_QT
|
||||||
|
|
||||||
int THUMB_SIZE = 128;
|
int THUMB_SIZE = 128;
|
||||||
int THUMB_SIZE_BORDER = 138;
|
int THUMB_SIZE_BORDER = 138;
|
||||||
@@ -43,7 +55,7 @@ void RawImage::allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type)
|
|||||||
m_width = w;
|
m_width = w;
|
||||||
m_height = h;
|
m_height = h;
|
||||||
m_channels = ch;
|
m_channels = ch;
|
||||||
m_ch = ch == 3 ? 4 : ch;
|
m_ch = ch > 1 ? 4 : ch;
|
||||||
m_origType = m_type = type;
|
m_origType = m_type = type;
|
||||||
m_pixels = std::make_unique<PixelType[]>((size_t)m_width * m_height * m_ch * typeSize(type));
|
m_pixels = std::make_unique<PixelType[]>((size_t)m_width * m_height * m_ch * typeSize(type));
|
||||||
}
|
}
|
||||||
@@ -78,6 +90,7 @@ RawImage::RawImage(RawImage &&d)
|
|||||||
m_thumbAspect = d.m_thumbAspect;
|
m_thumbAspect = d.m_thumbAspect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef NO_QT
|
||||||
RawImage::RawImage(const QImage &img)
|
RawImage::RawImage(const QImage &img)
|
||||||
{
|
{
|
||||||
qDebug() << img;
|
qDebug() << img;
|
||||||
@@ -145,6 +158,7 @@ RawImage::RawImage(const QImage &img)
|
|||||||
}
|
}
|
||||||
m_stats.m_stats = false;
|
m_stats.m_stats = false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
const RawImage::Stats& RawImage::imageStats() const
|
const RawImage::Stats& RawImage::imageStats() const
|
||||||
{
|
{
|
||||||
@@ -184,7 +198,7 @@ void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats)
|
|||||||
auto findMedian = [histSize](std::vector<uint32_t> &histogram, size_t n) -> size_t
|
auto findMedian = [histSize](std::vector<uint32_t> &histogram, size_t n) -> size_t
|
||||||
{
|
{
|
||||||
size_t histSum = 0;
|
size_t histSum = 0;
|
||||||
for(size_t o=0; o < histSize; o++)
|
for(size_t o=1; o < histSize; o++)
|
||||||
{
|
{
|
||||||
histSum += histogram[o];
|
histSum += histogram[o];
|
||||||
if(histSum >= n/2)
|
if(histSum >= n/2)
|
||||||
@@ -225,6 +239,57 @@ void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if constexpr(std::is_floating_point_v<T>)
|
||||||
|
{
|
||||||
|
T mmin = *std::min_element(min, min + 4);
|
||||||
|
T mmax = *std::max_element(max, max + 4);
|
||||||
|
|
||||||
|
T a = 1.0 / (mmax - mmin);
|
||||||
|
T b = -mmin / (mmax - mmin);
|
||||||
|
|
||||||
|
auto histFunc = [&](T d, int x)
|
||||||
|
{
|
||||||
|
uint16_t idx = std::clamp((T)(d * a + b) * histSize, (T)0.0, (T)65535.0);
|
||||||
|
histogram[x][idx]++;
|
||||||
|
};
|
||||||
|
|
||||||
|
// calculate histogram again
|
||||||
|
if(mmin < 0.0 || mmax > 1.0)
|
||||||
|
{
|
||||||
|
for(int i=0; i<4; i++)
|
||||||
|
{
|
||||||
|
histogram[i].clear();
|
||||||
|
histogram[i].resize(histSize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
histFunc(data[i*ch], 0);
|
||||||
|
if constexpr(ch >= 3)
|
||||||
|
{
|
||||||
|
histFunc(data[i*ch + 1], 1);
|
||||||
|
histFunc(data[i*ch + 2], 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr(ch == 1)
|
||||||
|
{
|
||||||
|
size_t h = (n / w) & (SIZE_MAX-1);
|
||||||
|
w &= (SIZE_MAX-1);
|
||||||
|
for(size_t y=0; y<h; y+=2)
|
||||||
|
{
|
||||||
|
for(size_t x=0; x<w; x+=2)
|
||||||
|
{
|
||||||
|
histFunc(data[y*w+x], 1);
|
||||||
|
histFunc(data[y*w+x+1], 2);
|
||||||
|
histFunc(data[(y+1)*w+x], 2);
|
||||||
|
histFunc(data[(y+1)*w+x+1], 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(int i = 0; i < 4; i++)
|
for(int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
stats.m_min[i] = min[i];
|
stats.m_min[i] = min[i];
|
||||||
@@ -234,7 +299,8 @@ void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats)
|
|||||||
double sum2 = (double)sum[i] * sum[i];
|
double sum2 = (double)sum[i] * sum[i];
|
||||||
stats.m_stdDev[i] = std::sqrt((sumSq[i] - sum2 / na[i]) / (na[i] - 1));
|
stats.m_stdDev[i] = std::sqrt((sumSq[i] - sum2 / na[i]) / (na[i] - 1));
|
||||||
|
|
||||||
uint32_t median = findMedian(histogram[i], na[i]);
|
size_t naclip = na[i] - histogram[i][0];
|
||||||
|
uint32_t median = findMedian(histogram[i], naclip);
|
||||||
stats.m_median[i] = median;
|
stats.m_median[i] = median;
|
||||||
std::vector<uint32_t> madHist(histSize, 0);
|
std::vector<uint32_t> madHist(histSize, 0);
|
||||||
madHist[0] = histogram[i][median];
|
madHist[0] = histogram[i][median];
|
||||||
@@ -243,7 +309,7 @@ void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats)
|
|||||||
if(median + o < histSize)madHist[o] += histogram[i][median + o];
|
if(median + o < histSize)madHist[o] += histogram[i][median + o];
|
||||||
if(o <= median)madHist[o] += histogram[i][median - o];
|
if(o <= median)madHist[o] += histogram[i][median - o];
|
||||||
}
|
}
|
||||||
stats.m_mad[i] = findMedian(madHist, na[i]);
|
stats.m_mad[i] = findMedian(madHist, naclip);
|
||||||
if constexpr(!std::numeric_limits<T>::is_integer)
|
if constexpr(!std::numeric_limits<T>::is_integer)
|
||||||
{
|
{
|
||||||
stats.m_median[i] /= 65535.0;
|
stats.m_median[i] /= 65535.0;
|
||||||
@@ -337,7 +403,12 @@ uint32_t RawImage::norm() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t RawImage::widthBytes() const
|
uint64_t RawImage::widthBytes() const
|
||||||
|
{
|
||||||
|
return (uint64_t)m_ch * m_width * typeSize(m_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t RawImage::widthSamples() const
|
||||||
{
|
{
|
||||||
return m_ch * m_width;
|
return m_ch * m_width;
|
||||||
}
|
}
|
||||||
@@ -408,13 +479,25 @@ void RawImage::convertToThumbnail()
|
|||||||
|
|
||||||
if(m_channels == 1)
|
if(m_channels == 1)
|
||||||
{
|
{
|
||||||
out[idx] = out[idx + 1] = out[idx + 2] = (F16)(in[idx2] * scale);
|
if(scale == 1.0f)
|
||||||
|
out[idx] = out[idx + 1] = out[idx + 2] = (F16)(in[idx2]);
|
||||||
|
else
|
||||||
|
out[idx] = out[idx + 1] = out[idx + 2] = (F16)(in[idx2] * scale);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
out[idx] = (F16)(in[idx2] * scale);
|
if(scale == 1.0f)
|
||||||
out[idx + 1] = (F16)(in[idx2 + 1] * scale);
|
{
|
||||||
out[idx + 2] = (F16)(in[idx2 + 2] * scale);
|
out[idx] = (F16)(in[idx2]);
|
||||||
|
out[idx + 1] = (F16)(in[idx2 + 1]);
|
||||||
|
out[idx + 2] = (F16)(in[idx2 + 2]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out[idx] = (F16)(in[idx2] * scale);
|
||||||
|
out[idx + 1] = (F16)(in[idx2 + 1] * scale);
|
||||||
|
out[idx + 2] = (F16)(in[idx2 + 2] * scale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out[idx + 3] = (F16)1.0f;
|
out[idx + 3] = (F16)1.0f;
|
||||||
}
|
}
|
||||||
@@ -439,7 +522,9 @@ void RawImage::convertToThumbnail()
|
|||||||
loop(out, reinterpret_cast<float*>(m_pixels.get()), 1.0f);
|
loop(out, reinterpret_cast<float*>(m_pixels.get()), 1.0f);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
#ifndef NO_QT
|
||||||
qWarning() << "FLOAT64 should not happend";
|
qWarning() << "FLOAT64 should not happend";
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,9 +573,9 @@ void convertType2(size_t size, const T *src, U *dst)
|
|||||||
|
|
||||||
if constexpr(std::is_integral_v<T> && (std::is_floating_point_v<U> || std::is_same_v<U, F16>))
|
if constexpr(std::is_integral_v<T> && (std::is_floating_point_v<U> || std::is_same_v<U, F16>))
|
||||||
{
|
{
|
||||||
U scale = (U)(1.0 / (double)std::numeric_limits<T>::max());
|
float scale = (float)(1.0 / (double)std::numeric_limits<T>::max());
|
||||||
for(size_t i = 0; i < size; i++)
|
for(size_t i = 0; i < size; i++)
|
||||||
dst[i] = (U)src[i] * scale;
|
dst[i] = (U)(src[i] * scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,6 +831,64 @@ void RawImage::resize(uint32_t w, uint32_t h)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T, typename U>
|
||||||
|
void integerResample(uint32_t w, uint32_t h, uint32_t ch, uint32_t oldw, uint32_t down, bool avg, const uint8_t *in_, uint8_t *out_)
|
||||||
|
{
|
||||||
|
const T *in = reinterpret_cast<const T*>(in_);
|
||||||
|
T *out = reinterpret_cast<T*>(out_);
|
||||||
|
const uint32_t down2 = down * down;
|
||||||
|
|
||||||
|
U m = std::numeric_limits<T>::max();
|
||||||
|
if constexpr(std::is_floating_point_v<T>)m = down2;
|
||||||
|
|
||||||
|
for(uint64_t i = 0; i < h; i++)
|
||||||
|
{
|
||||||
|
for(uint64_t o = 0; o < w; o++)
|
||||||
|
{
|
||||||
|
for(uint64_t p = 0; p < ch; p++)
|
||||||
|
{
|
||||||
|
U pix = 0;
|
||||||
|
for(uint32_t y = 0; y < down; y++)
|
||||||
|
for(uint32_t x = 0; x < down; x++)
|
||||||
|
pix += in[((i * down) + y) * oldw * ch + ((o * down) + x) * ch + p];
|
||||||
|
|
||||||
|
if (avg)
|
||||||
|
out[(i * w + o) * ch + p] = pix / down2;
|
||||||
|
else
|
||||||
|
out[(i * w + o) * ch + p] = std::min(pix, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RawImage::resizeInt(int downsample, bool avg)
|
||||||
|
{
|
||||||
|
uint32_t oldw = m_width;
|
||||||
|
std::unique_ptr<PixelType[]> old_pixels = std::move(m_pixels);
|
||||||
|
allocate(m_width / downsample, m_height / downsample, m_channels, m_type);
|
||||||
|
|
||||||
|
switch(m_type)
|
||||||
|
{
|
||||||
|
case RawImage::UINT8:
|
||||||
|
integerResample<uint8_t, uint32_t>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
|
||||||
|
break;
|
||||||
|
case RawImage::UINT16:
|
||||||
|
integerResample<uint16_t, uint32_t>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
|
||||||
|
break;
|
||||||
|
case RawImage::UINT32:
|
||||||
|
integerResample<uint32_t, uint64_t>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
|
||||||
|
break;
|
||||||
|
case RawImage::FLOAT32:
|
||||||
|
integerResample<float, double>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
|
||||||
|
break;
|
||||||
|
case RawImage::FLOAT64:
|
||||||
|
integerResample<double, double>(m_width, m_height, m_ch, oldw, downsample, avg, old_pixels.get(), m_pixels.get());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<float, float> RawImage::unitScale() const
|
std::pair<float, float> RawImage::unitScale() const
|
||||||
{
|
{
|
||||||
float min = *std::min_element(m_stats.m_min, m_stats.m_min + 4);
|
float min = *std::min_element(m_stats.m_min, m_stats.m_min + 4);
|
||||||
@@ -758,7 +901,7 @@ std::pair<float, float> RawImage::unitScale() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(min < 0.0f || max > 1.0f)
|
if(min < 0.0f || max > 1.0f)
|
||||||
return {1.0f / (max - min), min / (max - min)};
|
return {1.0f / (max - min), -min / (max - min)};
|
||||||
else
|
else
|
||||||
return {1.0f, 0.0f};
|
return {1.0f, 0.0f};
|
||||||
}
|
}
|
||||||
@@ -922,6 +1065,7 @@ bool RawImage::valid() const
|
|||||||
return m_width > 0 && m_height > 0;
|
return m_width > 0 && m_height > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef NO_QT
|
||||||
void RawImage::setICCProfile(const QByteArray &icc)
|
void RawImage::setICCProfile(const QByteArray &icc)
|
||||||
{
|
{
|
||||||
if(icc.size())
|
if(icc.size())
|
||||||
@@ -973,12 +1117,12 @@ void RawImage::convertTosRGB()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
qDebug() << "Failed to create color transform";
|
//qDebug() << "Failed to create color transform";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
qDebug() << "Failed to open icc profile";
|
//qDebug() << "Failed to open icc profile";
|
||||||
}
|
}
|
||||||
|
|
||||||
cmsCloseProfile(inProfile);
|
cmsCloseProfile(inProfile);
|
||||||
@@ -1022,19 +1166,145 @@ void RawImage::generateLUT()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
qDebug() << "Failed to create color transform";
|
//qDebug() << "Failed to create color transform";
|
||||||
m_lut.clear();
|
m_lut.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
qDebug() << "Failed to open icc profile";
|
//qDebug() << "Failed to open icc profile";
|
||||||
m_lut.clear();
|
m_lut.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
cmsCloseProfile(inProfile);
|
cmsCloseProfile(inProfile);
|
||||||
cmsCloseProfile(outProfile);
|
cmsCloseProfile(outProfile);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void RawImage::applySTF(const MTFParam &mtfParams)
|
||||||
|
{
|
||||||
|
auto applyMTF = [&](auto *src) -> void
|
||||||
|
{
|
||||||
|
float s = 1.0f;
|
||||||
|
if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)
|
||||||
|
s = (float)std::numeric_limits<std::remove_reference_t<decltype(*src)>>::max();
|
||||||
|
|
||||||
|
auto unit = unitScale();
|
||||||
|
float iscale = 1.0f / s;
|
||||||
|
size_t len = size() * m_ch;
|
||||||
|
for(size_t i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
float x;
|
||||||
|
if(m_ch == 4)
|
||||||
|
{
|
||||||
|
size_t c = i & 0x3;
|
||||||
|
if(c < 3)
|
||||||
|
{
|
||||||
|
if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)x = src[i] * iscale;
|
||||||
|
else x = src[i] * unit.first + unit.second;
|
||||||
|
x = (x - mtfParams.blackPoint[c]) / (mtfParams.whitePoint[c] - mtfParams.blackPoint[c]);
|
||||||
|
x = std::clamp(x, 0.0f, 1.0f);
|
||||||
|
x = ((mtfParams.midPoint[c] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[c] - 1.0f) * x - mtfParams.midPoint[c]);
|
||||||
|
src[i] = x * s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)x = src[i] * iscale;
|
||||||
|
else x = src[i] * unit.first + unit.second;
|
||||||
|
x = (x - mtfParams.blackPoint[0]) / (mtfParams.whitePoint[0] - mtfParams.blackPoint[0]);
|
||||||
|
x = std::clamp(x, 0.0f, 1.0f);
|
||||||
|
x = ((mtfParams.midPoint[0] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[0] - 1.0f) * x - mtfParams.midPoint[0]);
|
||||||
|
src[i] = x * s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch(m_type)
|
||||||
|
{
|
||||||
|
case UINT8:
|
||||||
|
applyMTF(reinterpret_cast<uint8_t*>(m_pixels.get()));
|
||||||
|
break;
|
||||||
|
case UINT16:
|
||||||
|
applyMTF(reinterpret_cast<uint16_t*>(m_pixels.get()));
|
||||||
|
break;
|
||||||
|
case UINT32:
|
||||||
|
applyMTF(reinterpret_cast<uint32_t*>(m_pixels.get()));
|
||||||
|
break;
|
||||||
|
case FLOAT32:
|
||||||
|
applyMTF(reinterpret_cast<float*>(m_pixels.get()));
|
||||||
|
break;
|
||||||
|
case FLOAT64:
|
||||||
|
applyMTF(reinterpret_cast<double*>(m_pixels.get()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MTFParam RawImage::calcMTFParams(bool linked, bool debayer) const
|
||||||
|
{
|
||||||
|
const float BLACK_POINT_SIGMA = -2.8f;
|
||||||
|
const float MAD_TO_SIGMA = 1.4826f;
|
||||||
|
const float TARGET_BACKGROUND = 0.25f;
|
||||||
|
|
||||||
|
auto MTF = [](float x, float m)
|
||||||
|
{
|
||||||
|
if(x < 0)return 0.0f;
|
||||||
|
if(x > 1)return 1.0f;
|
||||||
|
return ((m - 1) * x) / ((2 * m - 1) * x - m);
|
||||||
|
};
|
||||||
|
|
||||||
|
MTFParam mtfParam;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
int ch = m_channels;
|
||||||
|
int o = 0;
|
||||||
|
if(debayer)
|
||||||
|
{
|
||||||
|
i = 1;
|
||||||
|
ch = 4;
|
||||||
|
o = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float bp2 = 0;
|
||||||
|
float mid2 = 0;
|
||||||
|
float max2 = 0;
|
||||||
|
for(; i < ch; i++)
|
||||||
|
{
|
||||||
|
double median, mad, max;
|
||||||
|
median = m_stats.m_median[i];
|
||||||
|
mad = m_stats.m_mad[i];
|
||||||
|
median /= norm();
|
||||||
|
bool a = median > 0.5 ? true : false;
|
||||||
|
mad /= norm();
|
||||||
|
max = 1.0f;
|
||||||
|
float bp = a || mad == 0.0f ? 0.0f : std::clamp(median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
|
||||||
|
if(a && mad != 0.0f)
|
||||||
|
max = std::clamp(median - mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
|
||||||
|
|
||||||
|
float mid = !a ? MTF(median - bp, TARGET_BACKGROUND) : MTF(TARGET_BACKGROUND, max - median);
|
||||||
|
mtfParam.blackPoint[i-o] = bp;
|
||||||
|
mtfParam.midPoint[i-o] = mid;
|
||||||
|
mtfParam.whitePoint[i-o] = max;
|
||||||
|
bp2 += bp;
|
||||||
|
mid2 += mid;
|
||||||
|
max2 = max > max2 ? max : max2;
|
||||||
|
}
|
||||||
|
if(ch == 1)
|
||||||
|
{
|
||||||
|
mtfParam.blackPoint[1] = mtfParam.blackPoint[2] = mtfParam.blackPoint[0];
|
||||||
|
mtfParam.midPoint[1] = mtfParam.midPoint[2] = mtfParam.midPoint[0];
|
||||||
|
mtfParam.whitePoint[1] = mtfParam.whitePoint[2] = mtfParam.whitePoint[0];
|
||||||
|
}
|
||||||
|
if(linked)
|
||||||
|
{
|
||||||
|
mtfParam.blackPoint[0] = mtfParam.blackPoint[1] = mtfParam.blackPoint[2] = bp2 / ch;
|
||||||
|
mtfParam.midPoint[0] = mtfParam.midPoint[1] = mtfParam.midPoint[2] = mid2 / ch;
|
||||||
|
mtfParam.whitePoint[0] = mtfParam.whitePoint[1] = mtfParam.whitePoint[2] = max2;
|
||||||
|
}
|
||||||
|
return mtfParam;
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<uint16_t> &RawImage::getLUT() const
|
const std::vector<uint16_t> &RawImage::getLUT() const
|
||||||
{
|
{
|
||||||
@@ -7,7 +7,10 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
|
#ifndef NO_QT
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
#endif
|
||||||
|
#include "mtfparam.h"
|
||||||
|
|
||||||
extern int THUMB_SIZE;
|
extern int THUMB_SIZE;
|
||||||
extern int THUMB_SIZE_BORDER;
|
extern int THUMB_SIZE_BORDER;
|
||||||
@@ -83,7 +86,9 @@ public:
|
|||||||
RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type);
|
RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type);
|
||||||
RawImage(const RawImage &d);
|
RawImage(const RawImage &d);
|
||||||
RawImage(RawImage &&d);
|
RawImage(RawImage &&d);
|
||||||
|
#ifndef NO_QT
|
||||||
RawImage(const QImage &img);
|
RawImage(const QImage &img);
|
||||||
|
#endif
|
||||||
const RawImage::Stats& imageStats() const;
|
const RawImage::Stats& imageStats() const;
|
||||||
void calcStats();
|
void calcStats();
|
||||||
uint32_t width() const;
|
uint32_t width() const;
|
||||||
@@ -92,7 +97,8 @@ public:
|
|||||||
uint64_t size() const;
|
uint64_t size() const;
|
||||||
DataType type() const;
|
DataType type() const;
|
||||||
uint32_t norm() const;
|
uint32_t norm() const;
|
||||||
uint32_t widthBytes() const;
|
uint64_t widthBytes() const;
|
||||||
|
uint32_t widthSamples() const;
|
||||||
void* data();
|
void* data();
|
||||||
const void* data() const;
|
const void* data() const;
|
||||||
void* data(uint32_t row, uint32_t col = 0);
|
void* data(uint32_t row, uint32_t col = 0);
|
||||||
@@ -107,6 +113,7 @@ public:
|
|||||||
float thumbAspect() const;
|
float thumbAspect() const;
|
||||||
bool pixel(int x, int y, double &r, double &g, double &b) const;
|
bool pixel(int x, int y, double &r, double &g, double &b) const;
|
||||||
void resize(uint32_t w, uint32_t h);
|
void resize(uint32_t w, uint32_t h);
|
||||||
|
void resizeInt(int downsample, bool avg);
|
||||||
std::pair<float, float> unitScale() const;
|
std::pair<float, float> unitScale() const;
|
||||||
void flip();
|
void flip();
|
||||||
|
|
||||||
@@ -116,10 +123,14 @@ public:
|
|||||||
static size_t typeSize(DataType type);
|
static size_t typeSize(DataType type);
|
||||||
std::vector<RawImage> split() const;
|
std::vector<RawImage> split() const;
|
||||||
bool valid() const;
|
bool valid() const;
|
||||||
|
#ifndef NO_QT
|
||||||
void setICCProfile(const QByteArray &icc);
|
void setICCProfile(const QByteArray &icc);
|
||||||
void setICCProfile(const LibXISF::ByteArray &icc);
|
void setICCProfile(const LibXISF::ByteArray &icc);
|
||||||
void convertTosRGB();
|
void convertTosRGB();
|
||||||
void generateLUT();
|
void generateLUT();
|
||||||
|
#endif
|
||||||
|
void applySTF(const MTFParam &mtfParams);
|
||||||
|
MTFParam calcMTFParams(bool linked = false, bool debayer = false) const;
|
||||||
const std::vector<uint16_t>& getLUT() const;
|
const std::vector<uint16_t>& getLUT() const;
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "rawimage.h"
|
|
||||||
|
|
||||||
#ifdef __SSE2__
|
#ifdef __SSE2__
|
||||||
#include <x86intrin.h>
|
#include <x86intrin.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
template<typename T, int ch>
|
template<typename T, int ch>
|
||||||
void fromPlanarSSE(const void *in, void *out, size_t count)
|
void fromPlanarSSE(const void *in, void *out, size_t count)
|
||||||
@@ -4,12 +4,13 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QJsonValue>
|
#include <QJsonValue>
|
||||||
|
#include <QJSValueIterator>
|
||||||
#include "loadrunable.h"
|
#include "loadrunable.h"
|
||||||
#include "rawimage.h"
|
#include "rawimage.h"
|
||||||
#include "loadrunable.h"
|
#include "loadimage.h"
|
||||||
#include "batchprocessing.h"
|
#include "batchprocessing.h"
|
||||||
#include <fitsio2.h>
|
#include <fitsio2.h>
|
||||||
#include "libXISF/libxisf.h"
|
#include "libxisf.h"
|
||||||
#ifdef PLATESOLVER
|
#ifdef PLATESOLVER
|
||||||
#include "solver.h"
|
#include "solver.h"
|
||||||
#endif // PLATESOLVER
|
#endif // PLATESOLVER
|
||||||
@@ -17,17 +18,18 @@
|
|||||||
namespace Script
|
namespace Script
|
||||||
{
|
{
|
||||||
|
|
||||||
ScriptEngine::ScriptEngine(BatchProcessing *parent)
|
ScriptEngine::ScriptEngine(Database *database, BatchProcessing *parent)
|
||||||
: _jsEngine(new QJSEngine(this))
|
: _jsEngine(new QJSEngine(this))
|
||||||
, _database(new Database(this))
|
, _database(database)
|
||||||
, _parent(parent)
|
, _parent(parent)
|
||||||
, _pool(new QThreadPool(this))
|
, _pool(new QThreadPool(this))
|
||||||
{
|
{
|
||||||
QJSValue core = _jsEngine->newQObject(this);
|
QJSValue core = _jsEngine->newQObject(this);
|
||||||
_jsEngine->globalObject().setProperty("core", core);
|
_jsEngine->globalObject().setProperty("core", core);
|
||||||
QJSValue fitsRecordObject = _jsEngine->newQMetaObject(&FITSRecordModify::staticMetaObject);
|
QJSValue fitsRecordObject = _jsEngine->newQMetaObject(&FITSRecordModify::staticMetaObject);
|
||||||
|
QJSValue textFile = _jsEngine->newQMetaObject(&TextFile::staticMetaObject);
|
||||||
_jsEngine->globalObject().setProperty("FITSRecordModify", fitsRecordObject);
|
_jsEngine->globalObject().setProperty("FITSRecordModify", fitsRecordObject);
|
||||||
_database->init(QLatin1String("scriptengine"));
|
_jsEngine->globalObject().setProperty("TextFile", textFile);
|
||||||
_semaphore.release(_pool->maxThreadCount());
|
_semaphore.release(_pool->maxThreadCount());
|
||||||
_pool->setThreadPriority(QThread::LowPriority);
|
_pool->setThreadPriority(QThread::LowPriority);
|
||||||
|
|
||||||
@@ -36,10 +38,12 @@ ScriptEngine::ScriptEngine(BatchProcessing *parent)
|
|||||||
#endif // PLATESOLVER
|
#endif // PLATESOLVER
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir)
|
void ScriptEngine::setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir, const QString &arg)
|
||||||
{
|
{
|
||||||
_scriptPath = scriptPath;
|
_scriptPath = scriptPath;
|
||||||
_paths = paths;
|
if(!arg.isNull())
|
||||||
|
_jsEngine->globalObject().setProperty("scriptarg", arg);
|
||||||
|
setPaths(paths);
|
||||||
_outputDir = outputDir + "/";
|
_outputDir = outputDir + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,17 +77,71 @@ void ScriptEngine::log(const QString &message)
|
|||||||
|
|
||||||
void ScriptEngine::mark(File *file)
|
void ScriptEngine::mark(File *file)
|
||||||
{
|
{
|
||||||
_database->mark(file->absoluteFilePath());
|
QString path = file->absoluteFilePath();
|
||||||
|
QMetaObject::invokeMethod(_database, [this, path](){ _database->mark(path); }, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::unmark(File *file)
|
void ScriptEngine::unmark(File *file)
|
||||||
{
|
{
|
||||||
_database->unmark(file->absoluteFilePath());
|
QString path = file->absoluteFilePath();
|
||||||
|
QMetaObject::invokeMethod(_database, [this, path](){ _database->unmark(path); }, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScriptEngine::isMarked(const File *file) const
|
bool ScriptEngine::isMarked(const File *file)
|
||||||
{
|
{
|
||||||
return _database->isMarked(file->absoluteFilePath());
|
bool ret;
|
||||||
|
QString path = file->absoluteFilePath();
|
||||||
|
QMetaObject::invokeMethod(_database, [this, path](){ return _database->isMarked(path); }, Qt::BlockingQueuedConnection, &ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJSValue ScriptEngine::getObjects(double ra, double dec, double distance)
|
||||||
|
{
|
||||||
|
QVector<SkyObject> objects;
|
||||||
|
QMetaObject::invokeMethod(_database, [this, ra, dec, distance](){
|
||||||
|
return _database->getObjects(ra - distance, ra + distance, dec - distance, dec + distance); }, Qt::BlockingQueuedConnection, &objects);
|
||||||
|
|
||||||
|
QJSValue ret = newArray(objects.size());
|
||||||
|
qint32 i = 0;
|
||||||
|
for(auto &object : objects)
|
||||||
|
{
|
||||||
|
QJSValue jsObj = newObject();
|
||||||
|
jsObj.setProperty("name", object.name);
|
||||||
|
jsObj.setProperty("name2", object.name2);
|
||||||
|
jsObj.setProperty("ra", object.skyPoint.RA());
|
||||||
|
jsObj.setProperty("dec", object.skyPoint.DEC());
|
||||||
|
jsObj.setProperty("mag", object.mag);
|
||||||
|
ret.setProperty(i++, jsObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJSValue ScriptEngine::getObjects(const QJSValue &bounds)
|
||||||
|
{
|
||||||
|
QVector<SkyObject> objects;
|
||||||
|
double minRa = bounds.property("minRA").toNumber();
|
||||||
|
double maxRa = bounds.property("maxRA").toNumber();
|
||||||
|
double minDec = bounds.property("minDEC").toNumber();
|
||||||
|
double maxDec = bounds.property("maxDEC").toNumber();
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(_database, [this, minRa, maxRa, minDec, maxDec](){
|
||||||
|
return _database->getObjects(minRa, maxRa, minDec, maxDec); }, Qt::BlockingQueuedConnection, &objects);
|
||||||
|
|
||||||
|
QJSValue ret = newArray(objects.size());
|
||||||
|
qint32 i = 0;
|
||||||
|
for(auto &object : objects)
|
||||||
|
{
|
||||||
|
QJSValue jsObj = newObject();
|
||||||
|
jsObj.setProperty("name", object.name);
|
||||||
|
jsObj.setProperty("name2", object.name2);
|
||||||
|
jsObj.setProperty("ra", object.skyPoint.RA());
|
||||||
|
jsObj.setProperty("dec", object.skyPoint.DEC());
|
||||||
|
jsObj.setProperty("mag", object.mag);
|
||||||
|
ret.setProperty(i++, jsObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::setMaxThread(int maxthread)
|
void ScriptEngine::setMaxThread(int maxthread)
|
||||||
@@ -130,6 +188,41 @@ QJSValue ScriptEngine::getItem(const QStringList &items, const QString &label, i
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJSValue ScriptEngine::question(const QString &question, const QStringList &buttons, const QString &title) const
|
||||||
|
{
|
||||||
|
QJSValue ret;
|
||||||
|
QMetaObject::invokeMethod(_parent, "question", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QJSValue, ret), Q_ARG(QString, question), Q_ARG(QStringList, buttons), Q_ARG(QString, title));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::plot(const QJSValue &graph)
|
||||||
|
{
|
||||||
|
QVariant graphV = graph.toVariant(QJSValue::ConvertJSObjects);
|
||||||
|
if(graphV.isValid())
|
||||||
|
QMetaObject::invokeMethod(_parent, "plot", Qt::QueuedConnection, Q_ARG(QVariant, graphV));
|
||||||
|
else
|
||||||
|
logError("Invalid value to be plotted");
|
||||||
|
}
|
||||||
|
|
||||||
|
QJSValue ScriptEngine::openFile(const QString &fileName, const QString &mode)
|
||||||
|
{
|
||||||
|
QFileInfo info(fileName);
|
||||||
|
if(!info.isAbsolute())
|
||||||
|
info = QFileInfo(outputDir() + fileName);
|
||||||
|
|
||||||
|
TextFile *textFile = new TextFile;
|
||||||
|
if(textFile->open(info.absoluteFilePath(), mode))
|
||||||
|
{
|
||||||
|
return _jsEngine->newQObject(textFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logError("Failed to open file " + fileName);
|
||||||
|
delete textFile;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ScriptEngine::convert(File *file, QString &outpath, const QString &format, const QVariantMap ¶ms, bool async)
|
bool ScriptEngine::convert(File *file, QString &outpath, const QString &format, const QVariantMap ¶ms, bool async)
|
||||||
{
|
{
|
||||||
QString path;
|
QString path;
|
||||||
@@ -166,6 +259,7 @@ bool ScriptEngine::convert(File *file, QString &outpath, const QString &format,
|
|||||||
#ifdef PLATESOLVER
|
#ifdef PLATESOLVER
|
||||||
void ScriptEngine::setSolverProfile(int index)
|
void ScriptEngine::setSolverProfile(int index)
|
||||||
{
|
{
|
||||||
|
index -= 1;
|
||||||
if(_solver && index >= SSolver::Parameters::DEFAULT && index < SSolver::Parameters::BIG_STARS)
|
if(_solver && index >= SSolver::Parameters::DEFAULT && index < SSolver::Parameters::BIG_STARS)
|
||||||
{
|
{
|
||||||
_solver->setParameters((SSolver::Parameters::ParametersProfile)index);
|
_solver->setParameters((SSolver::Parameters::ParametersProfile)index);
|
||||||
@@ -221,7 +315,7 @@ void ScriptEngine::setStartingSolution(const QJSValue &solution)
|
|||||||
if(solution.isObject())
|
if(solution.isObject())
|
||||||
{
|
{
|
||||||
if(solution.hasProperty("ra") && solution.hasProperty("dec") && solution.property("ra").isNumber() && solution.property("dec").isNumber())
|
if(solution.hasProperty("ra") && solution.hasProperty("dec") && solution.property("ra").isNumber() && solution.property("dec").isNumber())
|
||||||
_solver->setSearchPosition(solution.property("ra").toNumber(), solution.property("dec").toNumber());
|
_solver->setSearchPosition(solution.property("ra").toNumber() / 15.0, solution.property("dec").toNumber());
|
||||||
|
|
||||||
if(solution.hasProperty("pixscale") && solution.property("pixscale").isNumber())
|
if(solution.hasProperty("pixscale") && solution.property("pixscale").isNumber())
|
||||||
{
|
{
|
||||||
@@ -326,14 +420,73 @@ QJSValue ScriptEngine::newArray(uint size)
|
|||||||
return _jsEngine->newArray(size);
|
return _jsEngine->newArray(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::run()
|
QJSValue ScriptEngine::eval(const QString &program)
|
||||||
{
|
{
|
||||||
QJSValue jsPaths = _jsEngine->newArray(_paths.size());
|
QStringList stackTrace;
|
||||||
for(qsizetype i=0; i<_paths.size(); i++)
|
QJSValue result = _jsEngine->evaluate(program, QString(), 1, &stackTrace);
|
||||||
jsPaths.setProperty(i, _jsEngine->newQObject(new File(_paths[i].first, _paths[i].second, this)));
|
|
||||||
|
if(result.isError())
|
||||||
|
{
|
||||||
|
QString error = result.property("name").toString() + " on line " + result.property("lineNumber").toString() + " : " + result.toString();
|
||||||
|
error += "\n" + result.property("stack").toString();
|
||||||
|
emit newMessage(error, true);
|
||||||
|
}
|
||||||
|
else if(!result.isUndefined())
|
||||||
|
{
|
||||||
|
emit newMessage(result.toString(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ScriptEngine::complete(const QString &line)
|
||||||
|
{
|
||||||
|
QStringList complete;
|
||||||
|
|
||||||
|
QJSValue globObj = _jsEngine->globalObject();
|
||||||
|
|
||||||
|
QRegularExpression reg("[a-zA-Z_][a-zA-Z0-9_]*");
|
||||||
|
auto match = reg.match(line);
|
||||||
|
if(match.hasMatch())
|
||||||
|
{
|
||||||
|
QString var = match.captured();
|
||||||
|
if(globObj.hasProperty(var))
|
||||||
|
{
|
||||||
|
complete.clear();
|
||||||
|
QJSValueIterator it(globObj.property(var));
|
||||||
|
while(it.hasNext())
|
||||||
|
{
|
||||||
|
it.next();
|
||||||
|
if(it.name() != "constructor" && it.name() != "objectNameChanged")
|
||||||
|
complete.append(var + "." + it.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QJSValueIterator it(globObj);
|
||||||
|
while(it.hasNext())
|
||||||
|
{
|
||||||
|
it.next();
|
||||||
|
complete.append(it.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::setPaths(const QList<QPair<QString, QString> > &paths)
|
||||||
|
{
|
||||||
|
_paths = paths;
|
||||||
|
QJSValue jsPaths = _jsEngine->newArray(paths.size());
|
||||||
|
for(qsizetype i=0; i<paths.size(); i++)
|
||||||
|
jsPaths.setProperty(i, _jsEngine->newQObject(new File(paths[i].first, paths[i].second, this)));
|
||||||
|
|
||||||
_jsEngine->globalObject().setProperty("files", jsPaths);
|
_jsEngine->globalObject().setProperty("files", jsPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::run()
|
||||||
|
{
|
||||||
QFile scriptFile(_scriptPath);
|
QFile scriptFile(_scriptPath);
|
||||||
if(!scriptFile.open(QIODevice::ReadOnly))
|
if(!scriptFile.open(QIODevice::ReadOnly))
|
||||||
{
|
{
|
||||||
@@ -364,16 +517,18 @@ void File::loadFitsKeywords()
|
|||||||
{
|
{
|
||||||
_fitsKeywordsLoaded = true;
|
_fitsKeywordsLoaded = true;
|
||||||
ImageInfoData info;
|
ImageInfoData info;
|
||||||
if(suffix().toLower() == "xisf")
|
if(isXISF(suffix()))
|
||||||
{
|
{
|
||||||
readXISFHeader(_path, info);
|
readXISFHeader(_path, info);
|
||||||
}
|
}
|
||||||
else if(suffix().toLower() == "fits" || suffix().toLower() == "fit")
|
else if(isFITS(suffix()))
|
||||||
{
|
{
|
||||||
readFITSHeader(_path, info);
|
readFITSHeader(_path, info);
|
||||||
}
|
}
|
||||||
else return;
|
else return;
|
||||||
|
|
||||||
|
_wcs = info.wcs;
|
||||||
|
|
||||||
for(auto &record : info.fitsHeader)
|
for(auto &record : info.fitsHeader)
|
||||||
{
|
{
|
||||||
_fitsKeywords.append(record.key);
|
_fitsKeywords.append(record.key);
|
||||||
@@ -515,11 +670,11 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
|
|||||||
_fitsKeywordsLoaded = false;
|
_fitsKeywordsLoaded = false;
|
||||||
_fitsKeywords.clear();
|
_fitsKeywords.clear();
|
||||||
|
|
||||||
if(QRegularExpression("(fits?|fz|fts)", QRegularExpression::CaseInsensitiveOption).match(suffix()).hasMatch())
|
if(isFITS(suffix()))
|
||||||
{
|
{
|
||||||
fitsfile *file;
|
fitsfile *file;
|
||||||
int status = 0;
|
int status = 0;
|
||||||
QString path = makeMaxPath(_path);
|
QString path = makeUNCPath(_path);
|
||||||
fits_open_diskfile(&file, path.toLocal8Bit().data(), READWRITE, &status);
|
fits_open_diskfile(&file, path.toLocal8Bit().data(), READWRITE, &status);
|
||||||
int num = 0;
|
int num = 0;
|
||||||
fits_get_num_hdus(file, &num, &status);
|
fits_get_num_hdus(file, &num, &status);
|
||||||
@@ -532,16 +687,22 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
|
|||||||
int naxis;
|
int naxis;
|
||||||
long naxes[3] = {0};
|
long naxes[3] = {0};
|
||||||
int type = -1;
|
int type = -1;
|
||||||
|
std::vector<int> imageIdxs;
|
||||||
for(int i=1; i <= num; i++)
|
for(int i=1; i <= num; i++)
|
||||||
{
|
{
|
||||||
fits_movabs_hdu(file, i, IMAGE_HDU, &status);
|
fits_movabs_hdu(file, i, IMAGE_HDU, &status);
|
||||||
fits_get_hdu_type(file, &type, &status);
|
fits_get_hdu_type(file, &type, &status);
|
||||||
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
|
if(type == IMAGE_HDU)
|
||||||
if(type == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
|
{
|
||||||
break;
|
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
|
||||||
if(i == num)return false;
|
if(naxis >= 2 && naxis <= 3)imageIdxs.push_back(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(modify->_imageIdx >= imageIdxs.size())return false;
|
||||||
|
fits_movabs_hdu(file, imageIdxs[modify->_imageIdx], &type, &status);
|
||||||
|
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
|
||||||
|
|
||||||
for(auto &remove : modify->_remove)
|
for(auto &remove : modify->_remove)
|
||||||
{
|
{
|
||||||
int status = 0;//we ignore errors from here
|
int status = 0;//we ignore errors from here
|
||||||
@@ -638,24 +799,27 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
|
|||||||
|
|
||||||
return status == 0;
|
return status == 0;
|
||||||
}
|
}
|
||||||
else if(suffix().toLower() == "xisf")
|
else if(isXISF(suffix()))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LibXISF::XISFModify modifyXISF;
|
LibXISF::XISFModify modifyXISF;
|
||||||
QString in = makeMaxPath(absoluteFilePath());
|
QString in = makeUNCPath(absoluteFilePath());
|
||||||
QString out = in + "~";
|
QString out = in + "~";
|
||||||
modifyXISF.open(in.toLocal8Bit().data());
|
modifyXISF.open(in.toLocal8Bit().data());
|
||||||
qDebug() << "modify" << in << out;
|
qDebug() << "modify" << in << out;
|
||||||
|
|
||||||
for(auto &remove : modify->_remove)
|
for(auto &remove : modify->_remove)
|
||||||
modifyXISF.removeFITSKeyword(0, remove.toStdString());
|
modifyXISF.removeFITSKeyword(modify->_imageIdx, remove.toStdString());
|
||||||
|
|
||||||
for(auto &record : modify->_update)
|
for(auto &record : modify->_update)
|
||||||
modifyXISF.updateFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()}, true);
|
modifyXISF.updateFITSKeyword(modify->_imageIdx, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()}, true);
|
||||||
|
|
||||||
for(auto &record : modify->_add)
|
for(auto &record : modify->_add)
|
||||||
modifyXISF.addFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
|
modifyXISF.addFITSKeyword(modify->_imageIdx, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
|
||||||
|
|
||||||
|
for(auto &property : modify->_property)
|
||||||
|
modifyXISF.updateProperty(modify->_imageIdx, property);
|
||||||
|
|
||||||
modifyXISF.save(out.toLocal8Bit().toStdString());
|
modifyXISF.save(out.toLocal8Bit().toStdString());
|
||||||
modifyXISF.close();
|
modifyXISF.close();
|
||||||
@@ -664,6 +828,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
|
|||||||
}
|
}
|
||||||
catch(std::filesystem::filesystem_error &err)
|
catch(std::filesystem::filesystem_error &err)
|
||||||
{
|
{
|
||||||
|
if(_engine)_engine->newMessage("Failed to modify file " + _path + " " + err.what(), true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch(LibXISF::Error &err)
|
catch(LibXISF::Error &err)
|
||||||
@@ -732,7 +897,7 @@ QJSValue File::stats()
|
|||||||
{
|
{
|
||||||
ImageInfoData info;
|
ImageInfoData info;
|
||||||
std::shared_ptr<RawImage> rawImage;
|
std::shared_ptr<RawImage> rawImage;
|
||||||
loadImage(_path, info, rawImage);
|
loadImage(_path, info, rawImage, 0);
|
||||||
rawImage->calcStats();
|
rawImage->calcStats();
|
||||||
RawImage::Stats stats = rawImage->imageStats();
|
RawImage::Stats stats = rawImage->imageStats();
|
||||||
_stats = _engine->newObject();
|
_stats = _engine->newObject();
|
||||||
@@ -746,6 +911,24 @@ QJSValue File::stats()
|
|||||||
return _stats;
|
return _stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJSValue File::calculatedBounds()
|
||||||
|
{
|
||||||
|
QJSValue ret = _engine->newObject();
|
||||||
|
loadFitsKeywords();
|
||||||
|
if(_wcs)
|
||||||
|
{
|
||||||
|
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
|
||||||
|
_wcs->calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
|
||||||
|
ret.setProperty("minRA", minRa);
|
||||||
|
ret.setProperty("maxRA", maxRa);
|
||||||
|
ret.setProperty("minDEC", minDec);
|
||||||
|
ret.setProperty("maxDEC", maxDec);
|
||||||
|
ret.setProperty("crVal1", crVal1);
|
||||||
|
ret.setProperty("crVal2", crVal2);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef PLATESOLVER
|
#ifdef PLATESOLVER
|
||||||
QJSValue File::solve(bool updateHeader)
|
QJSValue File::solve(bool updateHeader)
|
||||||
{
|
{
|
||||||
@@ -764,11 +947,11 @@ QJSValue File::extractStars(bool hfr)
|
|||||||
}
|
}
|
||||||
#endif // PLATESOLVER
|
#endif // PLATESOLVER
|
||||||
|
|
||||||
ScriptEngineThread::ScriptEngineThread(BatchProcessing *parent) : QObject(parent)
|
ScriptEngineThread::ScriptEngineThread(Database *database, BatchProcessing *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
_thread = new QThread();
|
_thread = new QThread();
|
||||||
_thread->setObjectName("ScriptEngine");
|
_thread->setObjectName("ScriptEngine");
|
||||||
_engine = new ScriptEngine(parent);
|
_engine = new ScriptEngine(database, parent);
|
||||||
_engine->moveToThread(_thread);
|
_engine->moveToThread(_thread);
|
||||||
connect(_engine, &ScriptEngine::finished, _thread, &QThread::quit);
|
connect(_engine, &ScriptEngine::finished, _thread, &QThread::quit);
|
||||||
connect(_engine, &ScriptEngine::newMessage, this, &ScriptEngineThread::newMessage);
|
connect(_engine, &ScriptEngine::newMessage, this, &ScriptEngineThread::newMessage);
|
||||||
@@ -784,9 +967,9 @@ ScriptEngineThread::~ScriptEngineThread()
|
|||||||
if(_engine)_engine->interrupt();
|
if(_engine)_engine->interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngineThread::setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir)
|
void ScriptEngineThread::setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir, const QString &arg)
|
||||||
{
|
{
|
||||||
_engine->setParams(scriptPath, paths, outputDir);
|
_engine->setParams(scriptPath, paths, outputDir, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngineThread::start()
|
void ScriptEngineThread::start()
|
||||||
@@ -815,4 +998,75 @@ void FITSRecordModify::addKeyword(const QString &key, const QVariant &value, con
|
|||||||
_update.append({key.toLatin1(), value, comment.toLatin1()});
|
_update.append({key.toLatin1(), value, comment.toLatin1()});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FITSRecordModify::updateProperty(const QString &id, const LibXISF::Variant &value)
|
||||||
|
{
|
||||||
|
_property.append(LibXISF::Property(id.toStdString(), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t FITSRecordModify::imageIndex() const
|
||||||
|
{
|
||||||
|
return _imageIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FITSRecordModify::setImageIndex(uint32_t idx)
|
||||||
|
{
|
||||||
|
_imageIdx = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextFile::open(const QString &path, const QString &mode)
|
||||||
|
{
|
||||||
|
_fr.setFileName(path);
|
||||||
|
QIODevice::OpenMode openMode;
|
||||||
|
if(mode == "r")
|
||||||
|
openMode = QIODevice::ReadOnly;
|
||||||
|
else if(mode == "w")
|
||||||
|
openMode = QIODevice::WriteOnly;
|
||||||
|
else if(mode == "a")
|
||||||
|
openMode = QIODevice::WriteOnly | QIODevice::Append;
|
||||||
|
else if(mode == "r+")
|
||||||
|
openMode = QIODevice::ReadWrite | QIODevice::ExistingOnly;
|
||||||
|
else if(mode == "w+")
|
||||||
|
openMode = QIODevice::ReadWrite;
|
||||||
|
else if(mode == "a+")
|
||||||
|
openMode = QIODevice::ReadWrite | QIODevice::Append;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
|
||||||
|
openMode |= QIODevice::Text;//always open as text
|
||||||
|
return _fr.open(openMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextFile::write(const QString &data)
|
||||||
|
{
|
||||||
|
_fr.write(data.toUtf8());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TextFile::read(int maxlen)
|
||||||
|
{
|
||||||
|
QByteArray data = _fr.read(maxlen);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TextFile::readLine()
|
||||||
|
{
|
||||||
|
QByteArray data = _fr.readLine();
|
||||||
|
return QString::fromUtf8(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TextFile::readAll()
|
||||||
|
{
|
||||||
|
QByteArray data = _fr.readAll();
|
||||||
|
return QString::fromUtf8(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextFile::seek(qint64 offset)
|
||||||
|
{
|
||||||
|
return _fr.seek(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 TextFile::pos()
|
||||||
|
{
|
||||||
|
return _fr.pos();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,8 @@
|
|||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QSemaphore>
|
#include <QSemaphore>
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "imageinfo.h"
|
#include "imageinfodata.h"
|
||||||
|
#include "libxisf.h"
|
||||||
|
|
||||||
class BatchProcessing;
|
class BatchProcessing;
|
||||||
class Solver;
|
class Solver;
|
||||||
@@ -31,8 +32,8 @@ class ScriptEngine : public QObject
|
|||||||
QList<QPair<QString, QString>> _paths;
|
QList<QPair<QString, QString>> _paths;
|
||||||
Solver *_solver = nullptr;
|
Solver *_solver = nullptr;
|
||||||
public:
|
public:
|
||||||
explicit ScriptEngine(BatchProcessing *parent = nullptr);
|
explicit ScriptEngine(Database *database, BatchProcessing *parent = nullptr);
|
||||||
void setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir);
|
void setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir, const QString &arg);
|
||||||
void reportError(const QString &message);
|
void reportError(const QString &message);
|
||||||
const QString& outputDir() const;
|
const QString& outputDir() const;
|
||||||
void interrupt();
|
void interrupt();
|
||||||
@@ -40,13 +41,18 @@ public:
|
|||||||
Q_INVOKABLE void log(const QString &message);
|
Q_INVOKABLE void log(const QString &message);
|
||||||
Q_INVOKABLE void mark(File *file);
|
Q_INVOKABLE void mark(File *file);
|
||||||
Q_INVOKABLE void unmark(File *file);
|
Q_INVOKABLE void unmark(File *file);
|
||||||
Q_INVOKABLE bool isMarked(const File *file) const;
|
Q_INVOKABLE bool isMarked(const File *file);
|
||||||
|
Q_INVOKABLE QJSValue getObjects(double ra, double dec, double distance);
|
||||||
|
Q_INVOKABLE QJSValue getObjects(const QJSValue &bounds);
|
||||||
Q_INVOKABLE void setMaxThread(int maxthread);
|
Q_INVOKABLE void setMaxThread(int maxthread);
|
||||||
Q_INVOKABLE void sync();
|
Q_INVOKABLE void sync();
|
||||||
Q_INVOKABLE QJSValue getString(const QString &label = QString(), const QString &text = QString()) const;
|
Q_INVOKABLE QJSValue getString(const QString &label = QString(), const QString &text = QString()) const;
|
||||||
Q_INVOKABLE QJSValue getInt(const QString &label = QString(), int value = 0);
|
Q_INVOKABLE QJSValue getInt(const QString &label = QString(), int value = 0);
|
||||||
Q_INVOKABLE QJSValue getFloat(const QString &label = QString(), double value = 0, int decimals = 3) const;
|
Q_INVOKABLE QJSValue getFloat(const QString &label = QString(), double value = 0, int decimals = 3) const;
|
||||||
Q_INVOKABLE QJSValue getItem(const QStringList &items, const QString &label = "", int current = 0) const;
|
Q_INVOKABLE QJSValue getItem(const QStringList &items, const QString &label = "", int current = 0) const;
|
||||||
|
Q_INVOKABLE QJSValue question(const QString &question, const QStringList &buttons = {"ok"}, const QString &title = "") const;
|
||||||
|
Q_INVOKABLE void plot(const QJSValue &pointsArray);
|
||||||
|
Q_INVOKABLE QJSValue openFile(const QString &fileName, const QString &mode = "r");
|
||||||
bool convert(File *file, QString &outpath, const QString &format, const QVariantMap ¶ms, bool async);
|
bool convert(File *file, QString &outpath, const QString &format, const QVariantMap ¶ms, bool async);
|
||||||
#ifdef PLATESOLVER
|
#ifdef PLATESOLVER
|
||||||
Q_INVOKABLE void setSolverProfile(int index);
|
Q_INVOKABLE void setSolverProfile(int index);
|
||||||
@@ -58,6 +64,9 @@ public:
|
|||||||
#endif // PLATESOLVER
|
#endif // PLATESOLVER
|
||||||
QJSValue newObject();
|
QJSValue newObject();
|
||||||
QJSValue newArray(uint size);
|
QJSValue newArray(uint size);
|
||||||
|
QJSValue eval(const QString &program);
|
||||||
|
QStringList complete(const QString &line);
|
||||||
|
void setPaths(const QList<QPair<QString, QString>> &paths);
|
||||||
public slots:
|
public slots:
|
||||||
void run();
|
void run();
|
||||||
signals:
|
signals:
|
||||||
@@ -71,9 +80,9 @@ class ScriptEngineThread : public QObject
|
|||||||
QThread *_thread;
|
QThread *_thread;
|
||||||
ScriptEngine *_engine;
|
ScriptEngine *_engine;
|
||||||
public:
|
public:
|
||||||
ScriptEngineThread(BatchProcessing *parent = nullptr);
|
ScriptEngineThread(Database *database, BatchProcessing *parent = nullptr);
|
||||||
~ScriptEngineThread();
|
~ScriptEngineThread();
|
||||||
void setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir);
|
void setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir, const QString &arg);
|
||||||
void start();
|
void start();
|
||||||
void interrupt();
|
void interrupt();
|
||||||
signals:
|
signals:
|
||||||
@@ -93,6 +102,7 @@ class File : public QObject
|
|||||||
bool _fitsKeywordsLoaded = false;
|
bool _fitsKeywordsLoaded = false;
|
||||||
QStringList _fitsKeywords;
|
QStringList _fitsKeywords;
|
||||||
QMultiHash<QString, FITSRecord> _fitsRecords;
|
QMultiHash<QString, FITSRecord> _fitsRecords;
|
||||||
|
std::shared_ptr<WCSDataT> _wcs;
|
||||||
void loadFitsKeywords();
|
void loadFitsKeywords();
|
||||||
bool mkpath(const QString &path) const;
|
bool mkpath(const QString &path) const;
|
||||||
QJSValue _stats;
|
QJSValue _stats;
|
||||||
@@ -121,6 +131,7 @@ public:
|
|||||||
Q_INVOKABLE File* convert(const QString &outpath, const QString &format, const QVariantMap ¶ms = QVariantMap());
|
Q_INVOKABLE File* convert(const QString &outpath, const QString &format, const QVariantMap ¶ms = QVariantMap());
|
||||||
Q_INVOKABLE File* convertAsync(const QString &outpath, const QString &format, const QVariantMap ¶ms = QVariantMap());
|
Q_INVOKABLE File* convertAsync(const QString &outpath, const QString &format, const QVariantMap ¶ms = QVariantMap());
|
||||||
Q_INVOKABLE QJSValue stats();
|
Q_INVOKABLE QJSValue stats();
|
||||||
|
Q_INVOKABLE QJSValue calculatedBounds();
|
||||||
#ifdef PLATESOLVER
|
#ifdef PLATESOLVER
|
||||||
Q_INVOKABLE QJSValue solve(bool updateHeader = false);
|
Q_INVOKABLE QJSValue solve(bool updateHeader = false);
|
||||||
Q_INVOKABLE QJSValue extractStars(bool hfr);
|
Q_INVOKABLE QJSValue extractStars(bool hfr);
|
||||||
@@ -133,6 +144,8 @@ class FITSRecordModify : public QObject
|
|||||||
QStringList _remove;
|
QStringList _remove;
|
||||||
QVector<FITSRecord> _update;
|
QVector<FITSRecord> _update;
|
||||||
QVector<FITSRecord> _add;
|
QVector<FITSRecord> _add;
|
||||||
|
QVector<LibXISF::Property> _property;
|
||||||
|
uint32_t _imageIdx = 0;
|
||||||
|
|
||||||
friend class File;
|
friend class File;
|
||||||
public:
|
public:
|
||||||
@@ -140,6 +153,24 @@ public:
|
|||||||
Q_INVOKABLE void removeKeyword(const QString &key);
|
Q_INVOKABLE void removeKeyword(const QString &key);
|
||||||
Q_INVOKABLE void updateKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
|
Q_INVOKABLE void updateKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
|
||||||
Q_INVOKABLE void addKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
|
Q_INVOKABLE void addKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
|
||||||
|
Q_PROPERTY(uint32_t imageIndex READ imageIndex WRITE setImageIndex);
|
||||||
|
void updateProperty(const QString &id, const LibXISF::Variant &value);
|
||||||
|
uint32_t imageIndex() const;
|
||||||
|
void setImageIndex(uint32_t idx);
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextFile : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QFile _fr;
|
||||||
|
public:
|
||||||
|
bool open(const QString &path, const QString &mode);
|
||||||
|
Q_INVOKABLE void write(const QString &data);
|
||||||
|
Q_INVOKABLE QString read(int maxlen);
|
||||||
|
Q_INVOKABLE QString readLine();
|
||||||
|
Q_INVOKABLE QString readAll();
|
||||||
|
Q_INVOKABLE bool seek(qint64 offset);
|
||||||
|
Q_INVOKABLE qint64 pos();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,21 @@
|
|||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QColorDialog>
|
||||||
#include "rawimage.h"
|
#include "rawimage.h"
|
||||||
|
|
||||||
extern int DEFAULT_WIDTH;
|
extern int DEFAULT_WIDTH;
|
||||||
extern double SATURATION;
|
extern double SATURATION;
|
||||||
extern int FILTERING;
|
extern int FILTERING;
|
||||||
|
extern bool BESTFIT;
|
||||||
|
extern QMap<QString, QColor> headerHighlight;
|
||||||
|
|
||||||
class EvenNumber : public QSpinBox
|
class EvenNumber : public QSpinBox
|
||||||
{
|
{
|
||||||
@@ -75,13 +85,88 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
|
|||||||
m_qualityThumbnail->setChecked(QUALITY_RESIZE);
|
m_qualityThumbnail->setChecked(QUALITY_RESIZE);
|
||||||
m_qualityThumbnail->setToolTip(tr("Use box filter when downsampling thumbnails instead of nearest. Slightly slower."));
|
m_qualityThumbnail->setToolTip(tr("Use box filter when downsampling thumbnails instead of nearest. Slightly slower."));
|
||||||
|
|
||||||
|
m_bestFit = new QCheckBox(tr("Best Fit on image load"));
|
||||||
|
m_bestFit->setToolTip(tr("Set Best Fit zoom level when opening new image."));
|
||||||
|
m_bestFit->setChecked(BESTFIT);
|
||||||
|
|
||||||
|
m_headerHighlight = new QListWidget(this);
|
||||||
|
m_headerHighlight->setToolTip(tr("List of FITS keywords that will be highlighted in Image info"));
|
||||||
|
for(auto i = headerHighlight.begin(); i != headerHighlight.end(); i++)
|
||||||
|
{
|
||||||
|
QListWidgetItem *item = new QListWidgetItem(m_headerHighlight);
|
||||||
|
item->setText(i.key());
|
||||||
|
item->setBackground(i.value());
|
||||||
|
}
|
||||||
|
m_keyword = new QLineEdit(this);
|
||||||
|
m_keyword->setPlaceholderText(tr("FITS keyword"));
|
||||||
|
QPushButton *color = new QPushButton(this);
|
||||||
|
QPixmap pix(16, 16);
|
||||||
|
pix.fill(m_color);
|
||||||
|
color->setIcon(pix);
|
||||||
|
connect(color, &QPushButton::clicked, [this, color](){
|
||||||
|
QColor rgb = QColorDialog::getColor(m_color, this);
|
||||||
|
if(rgb.isValid())
|
||||||
|
{
|
||||||
|
QPixmap pix(16, 16);
|
||||||
|
pix.fill(rgb);
|
||||||
|
color->setIcon(pix);
|
||||||
|
m_color = rgb;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QPushButton *add = new QPushButton(tr("Add keyword highlight"), this);
|
||||||
|
connect(add, &QPushButton::clicked, [this](){
|
||||||
|
auto list = m_headerHighlight->findItems(m_keyword->text(), Qt::MatchFixedString | Qt::MatchCaseSensitive);
|
||||||
|
if(list.size())return;
|
||||||
|
QListWidgetItem *item = new QListWidgetItem(m_headerHighlight);
|
||||||
|
item->setText(m_keyword->text());
|
||||||
|
item->setBackground(m_color);
|
||||||
|
});
|
||||||
|
QPushButton *remove = new QPushButton(tr("Remove keyword highlight"), this);
|
||||||
|
connect(remove, &QPushButton::clicked, [this](){
|
||||||
|
auto list = m_headerHighlight->selectedItems();
|
||||||
|
for(auto item : list)
|
||||||
|
delete item;
|
||||||
|
});
|
||||||
|
|
||||||
|
m_lang = new QComboBox(this);
|
||||||
|
m_lang->addItems({"English", "Français", "Slovenčina", "Português"});
|
||||||
|
QString lang;
|
||||||
|
switch(QLocale().language())
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case QLocale::English: lang = "en"; break;
|
||||||
|
case QLocale::French: lang = "fr"; break;
|
||||||
|
case QLocale::Slovak: lang = "sk"; break;
|
||||||
|
case QLocale::Portuguese: lang = "pt_BR"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lang = settings.value("settings/lang", lang).toString();
|
||||||
|
if(lang == "en")m_lang->setCurrentIndex(0);
|
||||||
|
else if(lang == "fr")m_lang->setCurrentIndex(1);
|
||||||
|
else if(lang == "sk")m_lang->setCurrentIndex(2);
|
||||||
|
else if(lang == "pt_BR")m_lang->setCurrentIndex(3);
|
||||||
|
|
||||||
layout->addRow(tr("Image preload count"), m_preloadImages);
|
layout->addRow(tr("Image preload count"), m_preloadImages);
|
||||||
layout->addRow(tr("Thumbnails size"), m_thumSize);
|
layout->addRow(tr("Thumbnails size"), m_thumSize);
|
||||||
layout->addRow(tr("Saturation"), m_saturation);
|
layout->addRow(tr("Saturation"), m_saturation);
|
||||||
layout->addRow(tr("Slideshow interval"), m_slideShowTime);
|
layout->addRow(tr("Slideshow interval"), m_slideShowTime);
|
||||||
layout->addRow(tr("Image interpolation"), m_filtering);
|
layout->addRow(tr("Image interpolation"), m_filtering);
|
||||||
|
layout->addRow(tr("Language"), m_lang);
|
||||||
layout->addRow(m_qualityThumbnail);
|
layout->addRow(m_qualityThumbnail);
|
||||||
layout->addRow(m_useNativeDialog);
|
layout->addRow(m_useNativeDialog);
|
||||||
|
layout->addRow(m_bestFit);
|
||||||
|
layout->addRow(new QLabel(tr("FITS header highlight"), this));
|
||||||
|
layout->addRow(m_headerHighlight);
|
||||||
|
layout->addRow(m_keyword, color);
|
||||||
|
layout->addRow(add, remove);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN64
|
||||||
|
QPushButton *installThumbnailer = new QPushButton(tr("Install"), this);
|
||||||
|
installThumbnailer->setToolTip(tr("This will install thumnail generation for FITS and XISF files in File Explorer"));
|
||||||
|
connect(installThumbnailer, &QPushButton::clicked, this, &SettingsDialog::installThumbnailer);
|
||||||
|
layout->addRow(tr("Install thumbnailer"), installThumbnailer);
|
||||||
|
#endif
|
||||||
//layout->addRow(new QLabel(tr("Changes in settings will take effect after program restart.")));
|
//layout->addRow(new QLabel(tr("Changes in settings will take effect after program restart.")));
|
||||||
|
|
||||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
|
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
|
||||||
@@ -103,6 +188,12 @@ void SettingsDialog::loadSettings()
|
|||||||
SATURATION = settings.value("settings/saturation", 95.0).toDouble() / 100.0;
|
SATURATION = settings.value("settings/saturation", 95.0).toDouble() / 100.0;
|
||||||
FILTERING = settings.value("settings/filtering", FILTERING).toInt();
|
FILTERING = settings.value("settings/filtering", FILTERING).toInt();
|
||||||
QUALITY_RESIZE = settings.value("settings/qualitythumbnail", QUALITY_RESIZE).toBool();
|
QUALITY_RESIZE = settings.value("settings/qualitythumbnail", QUALITY_RESIZE).toBool();
|
||||||
|
BESTFIT = settings.value("settings/bestfit", BESTFIT).toBool();
|
||||||
|
QStringList keywords = settings.value("settings/headerhighlightkeywords").toStringList();
|
||||||
|
QStringList colors = settings.value("settings/headerhighlightcolors").toStringList();
|
||||||
|
for(int i = 0; i < std::min(keywords.size(), colors.size()); i++)
|
||||||
|
headerHighlight.insert(keywords[i], QColor::fromString(colors[i]));
|
||||||
|
|
||||||
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, settings.value("settings/dontusenativedialogs", false).toBool());
|
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, settings.value("settings/dontusenativedialogs", false).toBool());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +207,25 @@ bool SettingsDialog::loadThumbsizes()
|
|||||||
return OLD_THUMB_SIZE != THUMB_SIZE;
|
return OLD_THUMB_SIZE != THUMB_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::installThumbnailer()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN64
|
||||||
|
QString path = QCoreApplication::instance()->applicationDirPath() + "/tenmonthumbnailer.dll";
|
||||||
|
if(!QFileInfo::exists(path))
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, tr("Missing dll"), tr("Can't find ") + path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QProcess regsvr;
|
||||||
|
int ret = regsvr.execute("regsvr32.exe", {"/s", path});
|
||||||
|
if(ret == 0)
|
||||||
|
QMessageBox::information(this, tr("Thumbnail support"), tr("Thumbnail generation support sucessufully installed."));
|
||||||
|
else
|
||||||
|
QMessageBox::critical(this, tr("Error"), tr("Failed to register thumbnailer. %1").arg(ret));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void SettingsDialog::saveSettings()
|
void SettingsDialog::saveSettings()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
@@ -127,9 +237,32 @@ void SettingsDialog::saveSettings()
|
|||||||
settings.setValue("settings/qualitythumbnail", m_qualityThumbnail->isChecked());
|
settings.setValue("settings/qualitythumbnail", m_qualityThumbnail->isChecked());
|
||||||
QUALITY_RESIZE = m_qualityThumbnail->isChecked();
|
QUALITY_RESIZE = m_qualityThumbnail->isChecked();
|
||||||
FILTERING = m_filtering->currentIndex();
|
FILTERING = m_filtering->currentIndex();
|
||||||
|
BESTFIT = m_bestFit->isChecked();
|
||||||
settings.setValue("settings/filtering", FILTERING);
|
settings.setValue("settings/filtering", FILTERING);
|
||||||
|
settings.setValue("settings/bestfit", BESTFIT);
|
||||||
SATURATION = m_saturation->value() / 100.0;
|
SATURATION = m_saturation->value() / 100.0;
|
||||||
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, m_useNativeDialog->isChecked());
|
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, m_useNativeDialog->isChecked());
|
||||||
if(DEFAULT_WIDTH != m_preloadImages->value())
|
if(DEFAULT_WIDTH != m_preloadImages->value())
|
||||||
emit preloadChanged(m_preloadImages->value());
|
emit preloadChanged(m_preloadImages->value());
|
||||||
|
|
||||||
|
headerHighlight.clear();
|
||||||
|
QStringList colors;
|
||||||
|
for(int i = 0; i < m_headerHighlight->count(); i++)
|
||||||
|
{
|
||||||
|
auto item = m_headerHighlight->item(i);
|
||||||
|
colors.push_back(item->background().color().name());
|
||||||
|
headerHighlight[item->text()] = item->background().color();
|
||||||
|
}
|
||||||
|
settings.setValue("settings/headerhighlightkeywords", headerHighlight.keys());
|
||||||
|
settings.setValue("settings/headerhighlightcolors", colors);
|
||||||
|
QString lang;
|
||||||
|
int langIdx = m_lang->currentIndex();
|
||||||
|
switch(langIdx)
|
||||||
|
{
|
||||||
|
case 0: lang = "en"; break;
|
||||||
|
case 1: lang = "fr"; break;
|
||||||
|
case 2: lang = "sk"; break;
|
||||||
|
case 3: lang = "pt_BR"; break;
|
||||||
|
}
|
||||||
|
settings.setValue("settings/lang", lang);
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
|
#include <QListWidget>
|
||||||
|
|
||||||
class SettingsDialog : public QDialog
|
class SettingsDialog : public QDialog
|
||||||
{
|
{
|
||||||
@@ -13,6 +14,8 @@ public:
|
|||||||
explicit SettingsDialog(QWidget *parent = nullptr);
|
explicit SettingsDialog(QWidget *parent = nullptr);
|
||||||
static void loadSettings();
|
static void loadSettings();
|
||||||
static bool loadThumbsizes();
|
static bool loadThumbsizes();
|
||||||
|
public slots:
|
||||||
|
void installThumbnailer();
|
||||||
signals:
|
signals:
|
||||||
void preloadChanged(int witdth);
|
void preloadChanged(int witdth);
|
||||||
private:
|
private:
|
||||||
@@ -25,6 +28,11 @@ private:
|
|||||||
QCheckBox *m_useNativeDialog;
|
QCheckBox *m_useNativeDialog;
|
||||||
QCheckBox *m_qualityThumbnail;
|
QCheckBox *m_qualityThumbnail;
|
||||||
QComboBox *m_filtering;
|
QComboBox *m_filtering;
|
||||||
|
QCheckBox *m_bestFit;
|
||||||
|
QListWidget *m_headerHighlight;
|
||||||
|
QColor m_color = Qt::yellow;
|
||||||
|
QLineEdit *m_keyword;
|
||||||
|
QComboBox *m_lang;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SETTINGSDIALOG_H
|
#endif // SETTINGSDIALOG_H
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
#include <wcslib/wcshdr.h>
|
#include <wcslib/wcshdr.h>
|
||||||
#include <wcslib/wcsutil.h>
|
#include <wcslib/wcsutil.h>
|
||||||
#include "rawimage.h"
|
#include "rawimage.h"
|
||||||
#include "loadrunable.h"
|
#include "loadimage.h"
|
||||||
#include "scriptengine.h"
|
#include "scriptengine.h"
|
||||||
|
|
||||||
Solver::Solver(QObject *parent) : QObject(parent)
|
Solver::Solver(QObject *parent) : QObject(parent)
|
||||||
@@ -41,7 +41,7 @@ bool Solver::loadImage(const QString &path)
|
|||||||
_loaded = false;
|
_loaded = false;
|
||||||
std::shared_ptr<RawImage> image;
|
std::shared_ptr<RawImage> image;
|
||||||
ImageInfoData info;
|
ImageInfoData info;
|
||||||
if(::loadImage(path, info, image, true))
|
if(::loadImage(path, info, image, 0, true))
|
||||||
{
|
{
|
||||||
return loadImage(image, path);
|
return loadImage(image, path);
|
||||||
}
|
}
|
||||||
@@ -186,8 +186,22 @@ bool Solver::updateHeader(QString &error)
|
|||||||
modify.updateKeyword("CTYPE2", "DEC--TAN", QByteArray("first parameter DEC, projection TANgential"));
|
modify.updateKeyword("CTYPE2", "DEC--TAN", QByteArray("first parameter DEC, projection TANgential"));
|
||||||
modify.updateKeyword("RADESYS", "ICRS", QByteArray("International Celestial Reference System"));
|
modify.updateKeyword("RADESYS", "ICRS", QByteArray("International Celestial Reference System"));
|
||||||
modify.updateKeyword("EQUINOX", 2000, QByteArray("Equinox of coordinates"));
|
modify.updateKeyword("EQUINOX", 2000, QByteArray("Equinox of coordinates"));
|
||||||
|
|
||||||
|
LibXISF::F64Matrix matrix(2, 2);
|
||||||
|
matrix(0, 0) = std::cos(rotationRad) * cdeltx;
|
||||||
|
matrix(0, 1) =-std::sin(rotationRad) * cdelty;
|
||||||
|
matrix(1, 0) = std::sin(rotationRad) * cdeltx;
|
||||||
|
matrix(1, 1) = std::cos(rotationRad) * cdelty;
|
||||||
|
|
||||||
|
modify.updateProperty("PCL:AstrometricSolution:ReferenceCelestialCoordinates", LibXISF::F64Vector({solution.ra, solution.dec}));
|
||||||
|
modify.updateProperty("PCL:AstrometricSolution:ReferenceImageCoordinates", LibXISF::F64Vector({_stats.width / 2.0, _stats.height / 2.0}));
|
||||||
|
modify.updateProperty("PCL:AstrometricSolution:LinearTransformationMatrix", LibXISF::F64Matrix(matrix));
|
||||||
|
modify.updateProperty("PCL:AstrometricSolution:ProjectionSystem", LibXISF::String("Gnomonic"));
|
||||||
|
modify.updateProperty("PCL:AstrometricSolution:ReferenceNativeCoordinates", LibXISF::F64Vector({0, 90}));
|
||||||
|
|
||||||
bool ret = file.modifyFITSRecords(&modify);
|
bool ret = file.modifyFITSRecords(&modify);
|
||||||
if(!ret)error = tr("Failed to update file header");
|
if(!ret)error = tr("Failed to update file header");
|
||||||
|
else emit headerUpdated(_path);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +46,7 @@ public slots:
|
|||||||
signals:
|
signals:
|
||||||
void solvingDone();
|
void solvingDone();
|
||||||
void extractionDone();
|
void extractionDone();
|
||||||
|
void headerUpdated(const QString &path);
|
||||||
void logOutput(const QString &log);
|
void logOutput(const QString &log);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ static float clamp(float x)
|
|||||||
|
|
||||||
STFSlider::STFSlider(const QColor &color, QWidget *parent) : QWidget(parent)
|
STFSlider::STFSlider(const QColor &color, QWidget *parent) : QWidget(parent)
|
||||||
{
|
{
|
||||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
setMinimumWidth(100);
|
setMinimumWidth(100);
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
|
|
||||||
@@ -64,12 +64,51 @@ void STFSlider::setMTFParams(float low, float mid, float high)
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void STFSlider::orientationChanged(Qt::Orientations orientation)
|
||||||
|
{
|
||||||
|
m_orientation = orientation;
|
||||||
|
if(m_orientation == Qt::Horizontal)
|
||||||
|
{
|
||||||
|
if(m_color == Qt::white)
|
||||||
|
{
|
||||||
|
setMaximumSize(QWIDGETSIZE_MAX, 16);
|
||||||
|
setMinimumSize(16, 16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setMaximumSize(QWIDGETSIZE_MAX, 10);
|
||||||
|
setMinimumSize(10, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(m_color == Qt::white)
|
||||||
|
{
|
||||||
|
setMaximumSize(16, QWIDGETSIZE_MAX);
|
||||||
|
setMinimumSize(16, 16);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setMaximumSize(10, QWIDGETSIZE_MAX);
|
||||||
|
setMinimumSize(10, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void STFSlider::paintEvent(QPaintEvent *event)
|
void STFSlider::paintEvent(QPaintEvent *event)
|
||||||
{
|
{
|
||||||
QPainter painter(this);
|
QPainter painter(this);
|
||||||
QRect rect = event->rect();
|
QRect rect = event->rect();
|
||||||
qreal w = rect.width() - 1;
|
qreal w = rect.width() - 1;
|
||||||
qreal h = rect.height();
|
qreal h = rect.height();
|
||||||
|
if(m_orientation == Qt::Vertical)
|
||||||
|
{
|
||||||
|
rect = rect.transposed();
|
||||||
|
painter.rotate(90);
|
||||||
|
w = rect.width() - 1;
|
||||||
|
h = rect.height();
|
||||||
|
painter.translate(0, -h);
|
||||||
|
}
|
||||||
QLinearGradient gradient(rect.topLeft(), rect.topRight());
|
QLinearGradient gradient(rect.topLeft(), rect.topRight());
|
||||||
gradient.setColorAt(0, Qt::black);
|
gradient.setColorAt(0, Qt::black);
|
||||||
for(int i=1; i<=32; i++)
|
for(int i=1; i<=32; i++)
|
||||||
@@ -93,6 +132,11 @@ void STFSlider::paintEvent(QPaintEvent *event)
|
|||||||
{
|
{
|
||||||
painter.setPen(p < m_threshold ? Qt::white : Qt::black);
|
painter.setPen(p < m_threshold ? Qt::white : Qt::black);
|
||||||
painter.resetTransform();
|
painter.resetTransform();
|
||||||
|
if(m_orientation == Qt::Vertical)
|
||||||
|
{
|
||||||
|
painter.rotate(90);
|
||||||
|
painter.translate(0, -h);
|
||||||
|
}
|
||||||
painter.translate(w*p, 0);
|
painter.translate(w*p, 0);
|
||||||
painter.drawPath(tick);
|
painter.drawPath(tick);
|
||||||
};
|
};
|
||||||
@@ -105,15 +149,26 @@ void STFSlider::paintEvent(QPaintEvent *event)
|
|||||||
|
|
||||||
void STFSlider::mouseMoveEvent(QMouseEvent *event)
|
void STFSlider::mouseMoveEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
const qreal x = event->position().x();
|
qreal x,w;
|
||||||
if(std::abs(m_blackPoint*width() - x) < 5 ||
|
if(m_orientation == Qt::Horizontal)
|
||||||
std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - x) < 5 ||
|
{
|
||||||
std::abs(m_whitePoint*width() - x) < 5)
|
x = event->position().x();
|
||||||
setCursor(Qt::SplitHCursor);
|
w = width();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x = event->position().y();
|
||||||
|
w = height();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(std::abs(m_blackPoint*w - x) < 5 ||
|
||||||
|
std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*w - x) < 5 ||
|
||||||
|
std::abs(m_whitePoint*w - x) < 5)
|
||||||
|
setCursor(m_orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor);
|
||||||
else
|
else
|
||||||
unsetCursor();
|
unsetCursor();
|
||||||
|
|
||||||
qreal xw = x/width();
|
qreal xw = x/w;
|
||||||
if(event->modifiers() & Qt::ShiftModifier && !m_fineTune)
|
if(event->modifiers() & Qt::ShiftModifier && !m_fineTune)
|
||||||
{
|
{
|
||||||
m_fineTune = true;
|
m_fineTune = true;
|
||||||
@@ -154,18 +209,29 @@ void STFSlider::mouseMoveEvent(QMouseEvent *event)
|
|||||||
|
|
||||||
void STFSlider::mousePressEvent(QMouseEvent *event)
|
void STFSlider::mousePressEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
const qreal x = event->position().x();
|
qreal x,w;
|
||||||
|
if(m_orientation == Qt::Horizontal)
|
||||||
|
{
|
||||||
|
x = event->position().x();
|
||||||
|
w = width();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x = event->position().y();
|
||||||
|
w = height();
|
||||||
|
}
|
||||||
|
|
||||||
if(event->modifiers() & Qt::ShiftModifier)
|
if(event->modifiers() & Qt::ShiftModifier)
|
||||||
{
|
{
|
||||||
m_fineTune = true;
|
m_fineTune = true;
|
||||||
m_fineTuneX = x/width();
|
m_fineTuneX = x/w;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - x) < 5)
|
if(std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*w - x) < 5)
|
||||||
m_grabbed = 1;
|
m_grabbed = 1;
|
||||||
else if(std::abs(m_blackPoint*width() - x) < 5)
|
else if(std::abs(m_blackPoint*w - x) < 5)
|
||||||
m_grabbed = 0;
|
m_grabbed = 0;
|
||||||
else if(std::abs(m_whitePoint*width() - x) < 5)
|
else if(std::abs(m_whitePoint*w - x) < 5)
|
||||||
m_grabbed = 2;
|
m_grabbed = 2;
|
||||||
else
|
else
|
||||||
m_grabbed = -1;
|
m_grabbed = -1;
|
||||||
@@ -15,12 +15,15 @@ class STFSlider : public QWidget
|
|||||||
float m_fineTuneX;
|
float m_fineTuneX;
|
||||||
QColor m_color;
|
QColor m_color;
|
||||||
float m_threshold;
|
float m_threshold;
|
||||||
|
Qt::Orientations m_orientation = Qt::Horizontal;
|
||||||
public:
|
public:
|
||||||
explicit STFSlider(const QColor &color = Qt::white, QWidget *parent = nullptr);
|
explicit STFSlider(const QColor &color = Qt::white, QWidget *parent = nullptr);
|
||||||
float blackPoint() const;
|
float blackPoint() const;
|
||||||
float midPoint() const;
|
float midPoint() const;
|
||||||
float whitePoint() const;
|
float whitePoint() const;
|
||||||
void setMTFParams(float low, float mid, float high);
|
void setMTFParams(float low, float mid, float high);
|
||||||
|
public slots:
|
||||||
|
void orientationChanged(Qt::Orientations orientation);
|
||||||
signals:
|
signals:
|
||||||
void paramChanged(float blackPoint, float midPoint, float whitePoint);
|
void paramChanged(float blackPoint, float midPoint, float whitePoint);
|
||||||
protected:
|
protected:
|
||||||
@@ -3,19 +3,9 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
#include <QStyle>
|
||||||
#include "imageringlist.h"
|
#include "imageringlist.h"
|
||||||
|
|
||||||
const float BLACK_POINT_SIGMA = -2.8f;
|
|
||||||
const float MAD_TO_SIGMA = 1.4826f;
|
|
||||||
const float TARGET_BACKGROUND = 0.25f;
|
|
||||||
|
|
||||||
float MTF(float x, float m)
|
|
||||||
{
|
|
||||||
if(x < 0)return 0;
|
|
||||||
if(x > 1)return 1;
|
|
||||||
return ((m - 1) * x) / ((2 * m - 1) * x - m);
|
|
||||||
}
|
|
||||||
|
|
||||||
StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar"), parent)
|
StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar"), parent)
|
||||||
{
|
{
|
||||||
setObjectName("stretchtoolbar");
|
setObjectName("stretchtoolbar");
|
||||||
@@ -23,16 +13,23 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
|
|||||||
QVBoxLayout *vbox1 = new QVBoxLayout(lum);
|
QVBoxLayout *vbox1 = new QVBoxLayout(lum);
|
||||||
m_stfSlider = new STFSlider(Qt::white, this);
|
m_stfSlider = new STFSlider(Qt::white, this);
|
||||||
vbox1->addWidget(m_stfSlider);
|
vbox1->addWidget(m_stfSlider);
|
||||||
|
connect(this, &StretchToolbar::orientationChanged, m_stfSlider, &STFSlider::orientationChanged);
|
||||||
|
|
||||||
m_stfSliderR = new STFSlider(Qt::red, this);
|
m_stfSliderR = new STFSlider(Qt::red, this);
|
||||||
m_stfSliderG = new STFSlider(Qt::green, this);
|
m_stfSliderG = new STFSlider(Qt::green, this);
|
||||||
m_stfSliderB = new STFSlider(Qt::blue, this);
|
m_stfSliderB = new STFSlider(Qt::blue, this);
|
||||||
QWidget *rgb = new QWidget(this);
|
QWidget *rgb = new QWidget(this);
|
||||||
QVBoxLayout *vbox2 = new QVBoxLayout(rgb);
|
QBoxLayout *box2 = new QBoxLayout(orientation() == Qt::Horizontal ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight, rgb);
|
||||||
vbox2->setSpacing(0);
|
box2->setSpacing(0);
|
||||||
vbox2->addWidget(m_stfSliderR);
|
box2->addWidget(m_stfSliderR);
|
||||||
vbox2->addWidget(m_stfSliderG);
|
box2->addWidget(m_stfSliderG);
|
||||||
vbox2->addWidget(m_stfSliderB);
|
box2->addWidget(m_stfSliderB);
|
||||||
|
connect(this, &StretchToolbar::orientationChanged, m_stfSliderR, &STFSlider::orientationChanged);
|
||||||
|
connect(this, &StretchToolbar::orientationChanged, m_stfSliderG, &STFSlider::orientationChanged);
|
||||||
|
connect(this, &StretchToolbar::orientationChanged, m_stfSliderB, &STFSlider::orientationChanged);
|
||||||
|
connect(this, &StretchToolbar::orientationChanged, [box2](Qt::Orientations orientation){
|
||||||
|
box2->setDirection(orientation == Qt::Horizontal ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight);
|
||||||
|
});
|
||||||
|
|
||||||
m_stack = new QStackedWidget(this);
|
m_stack = new QStackedWidget(this);
|
||||||
m_stack->addWidget(lum);
|
m_stack->addWidget(lum);
|
||||||
@@ -92,6 +89,11 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
|
|||||||
|
|
||||||
m_autoStretchOnLoad = addAction(QIcon(":/nuke_a.png"), tr("Apply auto stretch on load"));
|
m_autoStretchOnLoad = addAction(QIcon(":/nuke_a.png"), tr("Apply auto stretch on load"));
|
||||||
m_autoStretchOnLoad->setCheckable(true);
|
m_autoStretchOnLoad->setCheckable(true);
|
||||||
|
|
||||||
|
QAction *showGridButton = addAction(QIcon(":/grid.svg"), tr("Draw equatorial grid"));
|
||||||
|
showGridButton->setCheckable(true);
|
||||||
|
connect(showGridButton, &QAction::toggled, this, &StretchToolbar::drawGrid);
|
||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
m_autoStretchOnLoad->setChecked(settings.value("stretchtoolbar/autostretch", false).toBool());
|
m_autoStretchOnLoad->setChecked(settings.value("stretchtoolbar/autostretch", false).toBool());
|
||||||
}
|
}
|
||||||
@@ -102,58 +104,20 @@ StretchToolbar::~StretchToolbar()
|
|||||||
settings.setValue("stretchtoolbar/autostretch", m_autoStretchOnLoad->isChecked());
|
settings.setValue("stretchtoolbar/autostretch", m_autoStretchOnLoad->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MTFParam &StretchToolbar::params() const
|
||||||
|
{
|
||||||
|
return m_mtfParam;
|
||||||
|
}
|
||||||
|
|
||||||
void StretchToolbar::stretchImage(Image *img)
|
void StretchToolbar::stretchImage(Image *img)
|
||||||
{
|
{
|
||||||
if(img && img->rawImage())
|
if(img && img->rawImage())
|
||||||
{
|
{
|
||||||
const RawImage::Stats &stats = img->rawImage()->imageStats();
|
m_mtfParam = img->rawImage()->calcMTFParams(m_stack->currentIndex() == 0,
|
||||||
int i = 0;
|
m_stack->currentIndex() == 1 && img->rawImage()->channels() == 1 && m_debayer->isChecked());
|
||||||
int ch = 1;
|
|
||||||
int o = 0;
|
|
||||||
if(m_stack->currentIndex() == 1 && img->rawImage()->channels() == 1 && m_debayer->isChecked())
|
|
||||||
{
|
|
||||||
i = 1;
|
|
||||||
ch = 4;
|
|
||||||
o = 1;
|
|
||||||
}
|
|
||||||
if(img->rawImage()->channels() >= 3)
|
|
||||||
ch = 3;
|
|
||||||
|
|
||||||
float bp2 = 0;
|
|
||||||
float mid2 = 0;
|
|
||||||
float max2 = 0;
|
|
||||||
for(; i < ch; i++)
|
|
||||||
{
|
|
||||||
double median, mad, max;
|
|
||||||
median = stats.m_median[i];
|
|
||||||
mad = stats.m_mad[i];
|
|
||||||
max = stats.m_max[i];
|
|
||||||
median /= img->rawImage()->norm();
|
|
||||||
bool a = median > 0.5 ? true : false;
|
|
||||||
mad /= img->rawImage()->norm();
|
|
||||||
max = 1.0f;// /= img->rawImage()->norm();
|
|
||||||
float bp = a || mad == 0.0f ? 0.0f : std::clamp(median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
|
|
||||||
if(a && mad != 0.0f)
|
|
||||||
max = std::clamp(median - mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
|
|
||||||
float mid = !a ? MTF(median - bp, TARGET_BACKGROUND) : MTF(TARGET_BACKGROUND, max - median);
|
|
||||||
m_mtfParam.blackPoint[i-o] = bp;
|
|
||||||
m_mtfParam.midPoint[i-o] = mid;// / max;
|
|
||||||
m_mtfParam.whitePoint[i-o] = max;
|
|
||||||
bp2 += bp;
|
|
||||||
mid2 += mid;
|
|
||||||
max2 = max > max2 ? max : max2;
|
|
||||||
}
|
|
||||||
if(ch == 1)
|
|
||||||
{
|
|
||||||
m_mtfParam.blackPoint[1] = m_mtfParam.blackPoint[2] = m_mtfParam.blackPoint[0];
|
|
||||||
m_mtfParam.midPoint[1] = m_mtfParam.midPoint[2] = m_mtfParam.midPoint[0];
|
|
||||||
m_mtfParam.whitePoint[1] = m_mtfParam.whitePoint[2] = m_mtfParam.whitePoint[0];
|
|
||||||
}
|
|
||||||
if(m_stack->currentIndex() == 0)
|
if(m_stack->currentIndex() == 0)
|
||||||
{
|
{
|
||||||
m_mtfParam.blackPoint[0] = m_mtfParam.blackPoint[1] = m_mtfParam.blackPoint[2] = bp2 / ch;
|
|
||||||
m_mtfParam.midPoint[0] = m_mtfParam.midPoint[1] = m_mtfParam.midPoint[2] = mid2 / ch;
|
|
||||||
m_mtfParam.whitePoint[0] = m_mtfParam.whitePoint[1] = m_mtfParam.whitePoint[2] = max2;
|
|
||||||
m_stfSlider->setMTFParams(m_mtfParam.blackPoint[0], m_mtfParam.midPoint[0], m_mtfParam.whitePoint[0]);
|
m_stfSlider->setMTFParams(m_mtfParam.blackPoint[0], m_mtfParam.midPoint[0], m_mtfParam.whitePoint[0]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -4,16 +4,10 @@
|
|||||||
#include <QToolBar>
|
#include <QToolBar>
|
||||||
#include <QStackedWidget>
|
#include <QStackedWidget>
|
||||||
#include "stfslider.h"
|
#include "stfslider.h"
|
||||||
|
#include "mtfparam.h"
|
||||||
|
|
||||||
class Image;
|
class Image;
|
||||||
|
|
||||||
struct MTFParam
|
|
||||||
{
|
|
||||||
float blackPoint[3] = {0.0f, 0.0f, 0.0f};
|
|
||||||
float midPoint[3] = {0.5f, 0.5f, 0.5f};
|
|
||||||
float whitePoint[3] = {1.0f, 1.0f, 1.0f};
|
|
||||||
};
|
|
||||||
|
|
||||||
class StretchToolbar : public QToolBar
|
class StretchToolbar : public QToolBar
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -28,6 +22,7 @@ class StretchToolbar : public QToolBar
|
|||||||
public:
|
public:
|
||||||
explicit StretchToolbar(QWidget *parent = nullptr);
|
explicit StretchToolbar(QWidget *parent = nullptr);
|
||||||
~StretchToolbar();
|
~StretchToolbar();
|
||||||
|
const MTFParam& params() const;
|
||||||
public slots:
|
public slots:
|
||||||
void stretchImage(Image *img);
|
void stretchImage(Image *img);
|
||||||
void resetMTF();
|
void resetMTF();
|
||||||
@@ -39,6 +34,7 @@ signals:
|
|||||||
void invert(bool enable);
|
void invert(bool enable);
|
||||||
void superPixel(bool enable);
|
void superPixel(bool enable);
|
||||||
void falseColor(bool enable);
|
void falseColor(bool enable);
|
||||||
|
void drawGrid(bool enable);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // STRETCHTOOLBAR_H
|
#endif // STRETCHTOOLBAR_H
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef TFLOAT16_H
|
||||||
|
#define TFLOAT16_H
|
||||||
|
|
||||||
|
// crude implementation of float16 for platforms that do not support _Float16
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
class TFloat16
|
||||||
|
{
|
||||||
|
uint16_t b16;
|
||||||
|
public:
|
||||||
|
TFloat16(){ b16 = 0; }
|
||||||
|
explicit inline TFloat16(float f)
|
||||||
|
{
|
||||||
|
uint32_t i = *reinterpret_cast<uint32_t*>(&f);
|
||||||
|
uint32_t sign = (i >> 16) & 0x8000;
|
||||||
|
uint32_t exp = (i >> 23) & 0xff;
|
||||||
|
uint32_t mantisa = (i & 0x7fffff) >> 13;
|
||||||
|
|
||||||
|
b16 = 0;
|
||||||
|
if(exp < 111)
|
||||||
|
{
|
||||||
|
// do nothing it map to 0
|
||||||
|
}
|
||||||
|
else if(exp == 111)
|
||||||
|
{
|
||||||
|
b16 |= sign;
|
||||||
|
b16 |= mantisa;
|
||||||
|
}
|
||||||
|
else if(exp == 255)//inf or nan
|
||||||
|
{
|
||||||
|
b16 = 0x7c00;
|
||||||
|
b16 |= sign;
|
||||||
|
b16 |= mantisa;
|
||||||
|
}
|
||||||
|
else if(exp > 142)
|
||||||
|
{
|
||||||
|
b16 = 0x7c00;// inf
|
||||||
|
b16 |= sign;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
b16 |= sign;
|
||||||
|
b16 |= (exp - 112) << 10;
|
||||||
|
b16 |= mantisa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
friend TFloat16 operator*(TFloat16 a, TFloat16 b)
|
||||||
|
{
|
||||||
|
return TFloat16(static_cast<float>(a) * static_cast<float>(b));
|
||||||
|
}
|
||||||
|
operator float() const
|
||||||
|
{
|
||||||
|
uint32_t i = 0;
|
||||||
|
uint32_t sign = b16 & 0x8000;
|
||||||
|
uint32_t exp = (b16 & 0x7c00) >> 10;
|
||||||
|
if(b16)
|
||||||
|
{
|
||||||
|
i |= sign << 16;
|
||||||
|
if(exp==31)i |= 0x7f800000;
|
||||||
|
else i |= (exp + 112) << 23;
|
||||||
|
i |= (b16 & 0x3ff) << 13;
|
||||||
|
}
|
||||||
|
return *reinterpret_cast<float*>(&i);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TFLOAT16_H
|
||||||
-299
@@ -1,299 +0,0 @@
|
|||||||
#include "starfit.h"
|
|
||||||
#include <gsl/gsl_blas.h>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
const int PARAM_AM = 0;
|
|
||||||
const int PARAM_X0 = 1;
|
|
||||||
const int PARAM_Y0 = 2;
|
|
||||||
const int PARAM_SX = 3;
|
|
||||||
const int PARAM_SY = 4;
|
|
||||||
const int PARAM_TH = 5;
|
|
||||||
|
|
||||||
const int MAX_ITER = 20;
|
|
||||||
const double TOL = 1.0e-3;
|
|
||||||
|
|
||||||
struct StarData
|
|
||||||
{
|
|
||||||
size_t size;
|
|
||||||
std::vector<double> val;
|
|
||||||
};
|
|
||||||
|
|
||||||
// a * exp(-0.5*((x-x0)/sx)^2 + ((y-y0)/sy)^2)
|
|
||||||
double gauss_model(double a, double x0, double y0, double sx, double sy, double x, double y)
|
|
||||||
{
|
|
||||||
double _x = (x-x0)/sx;
|
|
||||||
double _y = (y-y0)/sy;
|
|
||||||
return a*exp(-0.5*(_x*_x + _y*_y));
|
|
||||||
}
|
|
||||||
|
|
||||||
int func_f(const gsl_vector *X, void *params, gsl_vector *f)
|
|
||||||
{
|
|
||||||
StarData *d = static_cast<StarData*>(params);
|
|
||||||
double am = gsl_vector_get(X, PARAM_AM);
|
|
||||||
double x0 = gsl_vector_get(X, PARAM_X0);
|
|
||||||
double y0 = gsl_vector_get(X, PARAM_Y0);
|
|
||||||
double sx = gsl_vector_get(X, PARAM_SX);
|
|
||||||
double sy = gsl_vector_get(X, PARAM_SY);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for(size_t y=0;y<d->size;y++)
|
|
||||||
{
|
|
||||||
for(size_t x=0;x<d->size;x++)
|
|
||||||
{
|
|
||||||
double v = gauss_model(am, x0, y0, sx, sy, x, y);
|
|
||||||
gsl_vector_set(f, i, d->val[i] - v);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GSL_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
int func_df(const gsl_vector *X, void *params, gsl_matrix *J)
|
|
||||||
{
|
|
||||||
StarData *d = static_cast<StarData*>(params);
|
|
||||||
double am = gsl_vector_get(X, PARAM_AM);
|
|
||||||
double x0 = gsl_vector_get(X, PARAM_X0);
|
|
||||||
double y0 = gsl_vector_get(X, PARAM_Y0);
|
|
||||||
double sx = gsl_vector_get(X, PARAM_SX);
|
|
||||||
double sy = gsl_vector_get(X, PARAM_SY);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for(size_t y=0;y<d->size;y++)
|
|
||||||
{
|
|
||||||
for(size_t x=0;x<d->size;x++)
|
|
||||||
{
|
|
||||||
double tx = x-x0;
|
|
||||||
double ty = y-y0;
|
|
||||||
double e = gauss_model(am, x0, y0, sx, sy, x, y);
|
|
||||||
|
|
||||||
gsl_matrix_set(J, i, PARAM_AM, -e/am);
|
|
||||||
gsl_matrix_set(J, i, PARAM_X0, -e*(tx/(sx*sx)));
|
|
||||||
gsl_matrix_set(J, i, PARAM_Y0, -e*(ty/(sy*sy)));
|
|
||||||
gsl_matrix_set(J, i, PARAM_SX, -e*(tx*tx/(sx*sx*sx)));
|
|
||||||
gsl_matrix_set(J, i, PARAM_SY, -e*(ty*ty/(sy*sy*sy)));
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GSL_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
int func_f_an(const gsl_vector *X, void *params, gsl_vector *f)
|
|
||||||
{
|
|
||||||
StarData *d = static_cast<StarData*>(params);
|
|
||||||
double am = gsl_vector_get(X, PARAM_AM);
|
|
||||||
double x0 = gsl_vector_get(X, PARAM_X0);
|
|
||||||
double y0 = gsl_vector_get(X, PARAM_Y0);
|
|
||||||
double sx = gsl_vector_get(X, PARAM_SX);
|
|
||||||
double sy = gsl_vector_get(X, PARAM_SY);
|
|
||||||
double th = gsl_vector_get(X, PARAM_TH);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
double a = sin(th);
|
|
||||||
double b = cos(th);
|
|
||||||
for(size_t y=0;y<d->size;y++)
|
|
||||||
{
|
|
||||||
for(size_t x=0;x<d->size;x++)
|
|
||||||
{
|
|
||||||
double v = gauss_model(am, x0, y0, sx, sy, x*b-y*a, x*a+y*b);
|
|
||||||
gsl_vector_set(f, i, d->val[i] - v);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GSL_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
int func_df_af(const gsl_vector *X, void *params, gsl_matrix *J)
|
|
||||||
{
|
|
||||||
StarData *d = static_cast<StarData*>(params);
|
|
||||||
double am = gsl_vector_get(X, PARAM_AM);
|
|
||||||
double x0 = gsl_vector_get(X, PARAM_X0);
|
|
||||||
double y0 = gsl_vector_get(X, PARAM_Y0);
|
|
||||||
double sx = gsl_vector_get(X, PARAM_SX);
|
|
||||||
double sy = gsl_vector_get(X, PARAM_SY);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for(size_t y=0;y<d->size;y++)
|
|
||||||
{
|
|
||||||
for(size_t x=0;x<d->size;x++)
|
|
||||||
{
|
|
||||||
double tx = x-x0;
|
|
||||||
double ty = y-y0;
|
|
||||||
double e = gauss_model(am, x0, y0, sx, sy, x, y);
|
|
||||||
|
|
||||||
gsl_matrix_set(J, i, PARAM_AM, -e/am);
|
|
||||||
gsl_matrix_set(J, i, PARAM_X0, -e*(tx/(sx*sx)));
|
|
||||||
gsl_matrix_set(J, i, PARAM_Y0, -e*(ty/(sy*sy)));
|
|
||||||
gsl_matrix_set(J, i, PARAM_SX, -e*(tx*tx/(sx*sx*sx)));
|
|
||||||
gsl_matrix_set(J, i, PARAM_SY, -e*(ty*ty/(sy*sy*sy)));
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GSL_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
//int func_fvv(const gsl_vector *x, const gsl_vector * v, void *params, gsl_vector *fvv)
|
|
||||||
//{
|
|
||||||
// return GSL_SUCCESS;
|
|
||||||
//}
|
|
||||||
|
|
||||||
void callback(const size_t iter, void *, const gsl_multifit_nlinear_workspace *w)
|
|
||||||
{
|
|
||||||
double rcond;
|
|
||||||
gsl_vector *x = gsl_multifit_nlinear_position(w);
|
|
||||||
gsl_multifit_nlinear_rcond(&rcond, w);
|
|
||||||
QString r = "Iter: " + QString::number(iter)
|
|
||||||
+ " Am: " + QString::number(gsl_vector_get(x, PARAM_AM))
|
|
||||||
+ " X0: " + QString::number(gsl_vector_get(x, PARAM_X0))
|
|
||||||
+ " Y0: " + QString::number(gsl_vector_get(x, PARAM_Y0))
|
|
||||||
+ " SX: " + QString::number(gsl_vector_get(x, PARAM_SX))
|
|
||||||
+ " SY: " + QString::number(gsl_vector_get(x, PARAM_SY))
|
|
||||||
+ " J(X) :" + QString::number(1.0/rcond)
|
|
||||||
+ " av: " + QString::number(gsl_multifit_nlinear_avratio(w));
|
|
||||||
std::cout << r.toStdString() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void callback_an(const size_t iter, void *, const gsl_multifit_nlinear_workspace *w)
|
|
||||||
{
|
|
||||||
double rcond;
|
|
||||||
gsl_vector *x = gsl_multifit_nlinear_position(w);
|
|
||||||
gsl_multifit_nlinear_rcond(&rcond, w);
|
|
||||||
qDebug() << "Iter:" << iter << "Am:" << gsl_vector_get(x, PARAM_AM)
|
|
||||||
<< "X0:" << gsl_vector_get(x, PARAM_X0)
|
|
||||||
<< "Y0:" << gsl_vector_get(x, PARAM_Y0)
|
|
||||||
<< "SX:" << gsl_vector_get(x, PARAM_SX)
|
|
||||||
<< "SY:" << gsl_vector_get(x, PARAM_SY)
|
|
||||||
<< "TH:" << gsl_vector_get(x, PARAM_TH)
|
|
||||||
<< "J(X):" << 1.0/rcond
|
|
||||||
<< "av:" << gsl_multifit_nlinear_avratio(w);
|
|
||||||
}
|
|
||||||
|
|
||||||
Star::Star()
|
|
||||||
{
|
|
||||||
m_am = m_x = m_y = m_sx = m_sy = NAN;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Star::valid() const
|
|
||||||
{
|
|
||||||
return !isnan(m_am);
|
|
||||||
}
|
|
||||||
|
|
||||||
//half width at half maximum = sqrt(2*ln(2))
|
|
||||||
double Star::hwhmX() const
|
|
||||||
{
|
|
||||||
return 1.177410023*m_sx;
|
|
||||||
}
|
|
||||||
|
|
||||||
double Star::hwhmY() const
|
|
||||||
{
|
|
||||||
return 1.177410023*m_sy;
|
|
||||||
}
|
|
||||||
|
|
||||||
// half width at 1/20 maximum
|
|
||||||
double Star::hw20X() const
|
|
||||||
{
|
|
||||||
return 2.447746831*m_sx;
|
|
||||||
}
|
|
||||||
|
|
||||||
double Star::hw20Y() const
|
|
||||||
{
|
|
||||||
return 2.447746831*m_sy;
|
|
||||||
}
|
|
||||||
|
|
||||||
// full width at half maximum
|
|
||||||
double Star::fwhmX() const
|
|
||||||
{
|
|
||||||
return 2.354820045*m_sx;
|
|
||||||
}
|
|
||||||
|
|
||||||
double Star::fwhmY() const
|
|
||||||
{
|
|
||||||
return 2.354820045*m_sy;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Star::operator<(const Star &d) const
|
|
||||||
{
|
|
||||||
return m_am < d.m_am;
|
|
||||||
}
|
|
||||||
|
|
||||||
StarFit::StarFit(int size)
|
|
||||||
{
|
|
||||||
m_size = size;
|
|
||||||
m_fdf_params = gsl_multifit_nlinear_default_parameters();
|
|
||||||
m_fdf_params.trs = gsl_multifit_nlinear_trs_lmaccel;
|
|
||||||
|
|
||||||
m_fdf.f = func_f;
|
|
||||||
m_fdf.df = func_df;
|
|
||||||
m_fdf.fvv = nullptr;
|
|
||||||
m_fdf.n = size*size;
|
|
||||||
m_fdf.p = 5;//number of model parameters amplitude, x, y, fwhm_x, fwhm_y
|
|
||||||
|
|
||||||
m_fdf_an.f = func_f_an;
|
|
||||||
m_fdf_an.df = nullptr;
|
|
||||||
m_fdf_an.fvv = nullptr;
|
|
||||||
m_fdf_an.n = size*size;
|
|
||||||
m_fdf_an.p = 6;//number of model parameters amplitude, x, y, sigma_x, sigma_y, angle
|
|
||||||
|
|
||||||
gsl_set_error_handler_off();
|
|
||||||
}
|
|
||||||
|
|
||||||
StarFit::~StarFit()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Star StarFit::fitStar(const std::vector<double> &data, bool angle)
|
|
||||||
{
|
|
||||||
gsl_multifit_nlinear_fdf *fdf = angle ? &m_fdf_an : &m_fdf;
|
|
||||||
Star star;
|
|
||||||
StarData d;
|
|
||||||
d.val = data;
|
|
||||||
d.size = m_size;
|
|
||||||
d.val = data;
|
|
||||||
fdf->params = &d;
|
|
||||||
int info;
|
|
||||||
|
|
||||||
double min = *std::min_element(data.begin(), data.end());
|
|
||||||
double max = *std::max_element(data.begin(), data.end()) - min;
|
|
||||||
for(double &v : d.val)
|
|
||||||
{
|
|
||||||
v -= min;
|
|
||||||
}
|
|
||||||
|
|
||||||
gsl_vector *start = gsl_vector_alloc(fdf->p);
|
|
||||||
gsl_vector_set(start, PARAM_AM, max);
|
|
||||||
gsl_vector_set(start, PARAM_X0, m_size/2);
|
|
||||||
gsl_vector_set(start, PARAM_Y0, m_size/2);
|
|
||||||
gsl_vector_set(start, PARAM_SX, 1.0);
|
|
||||||
gsl_vector_set(start, PARAM_SY, 1.0);
|
|
||||||
if(angle)
|
|
||||||
gsl_vector_set(start, PARAM_TH, 0.0);
|
|
||||||
|
|
||||||
gsl_multifit_nlinear_workspace *workspace = gsl_multifit_nlinear_alloc(gsl_multifit_nlinear_trust, &m_fdf_params, fdf->n, fdf->p);
|
|
||||||
|
|
||||||
int ret = gsl_multifit_nlinear_init(start, fdf, workspace);
|
|
||||||
if(ret)return star;
|
|
||||||
|
|
||||||
ret = gsl_multifit_nlinear_driver(MAX_ITER, TOL, TOL, TOL, nullptr, nullptr, &info, workspace);
|
|
||||||
|
|
||||||
if(ret==0)
|
|
||||||
{
|
|
||||||
gsl_vector *y = gsl_multifit_nlinear_position(workspace);
|
|
||||||
star.m_am = gsl_vector_get(y, PARAM_AM);
|
|
||||||
star.m_x = gsl_vector_get(y, PARAM_X0);
|
|
||||||
star.m_y = gsl_vector_get(y, PARAM_Y0);
|
|
||||||
star.m_sx = gsl_vector_get(y, PARAM_SX);
|
|
||||||
star.m_sy = gsl_vector_get(y, PARAM_SY);
|
|
||||||
if(angle)
|
|
||||||
star.m_theta = gsl_vector_get(y, PARAM_TH);
|
|
||||||
//qDebug() << "finished" << star.m_am << star.m_sx << star.m_sy;
|
|
||||||
}
|
|
||||||
|
|
||||||
gsl_vector_free(start);
|
|
||||||
gsl_multifit_nlinear_free(workspace);
|
|
||||||
|
|
||||||
return star;
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#ifndef STARFIT_H
|
|
||||||
#define STARFIT_H
|
|
||||||
|
|
||||||
#include "rawimage.h"
|
|
||||||
#include <gsl/gsl_multifit_nlinear.h>
|
|
||||||
|
|
||||||
double gauss_model(double a, double x0, double y0, double sx, double sy, double x, double y);
|
|
||||||
|
|
||||||
struct Star
|
|
||||||
{
|
|
||||||
double m_am;
|
|
||||||
double m_x,m_y;
|
|
||||||
double m_sx,m_sy;
|
|
||||||
double m_theta;
|
|
||||||
Star();
|
|
||||||
bool valid() const;
|
|
||||||
double hwhmX() const;
|
|
||||||
double hwhmY() const;
|
|
||||||
double hw20X() const;
|
|
||||||
double hw20Y() const;
|
|
||||||
double fwhmX() const;
|
|
||||||
double fwhmY() const;
|
|
||||||
bool operator<(const Star &d) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StarFit
|
|
||||||
{
|
|
||||||
int m_size;
|
|
||||||
gsl_multifit_nlinear_fdf m_fdf;
|
|
||||||
gsl_multifit_nlinear_fdf m_fdf_an;
|
|
||||||
gsl_multifit_nlinear_parameters m_fdf_params;
|
|
||||||
gsl_vector *m_vector;
|
|
||||||
public:
|
|
||||||
StarFit(int size);
|
|
||||||
~StarFit();
|
|
||||||
Star fitStar(const std::vector<double> &data, bool angle);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // STARFIT_H
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
option(BUILD_THUMBNAILER "Build generator of thumbnails" OFF)
|
||||||
|
|
||||||
|
if(BUILD_THUMBNAILER)
|
||||||
|
if(WIN32)
|
||||||
|
add_library(tenmonthumbnailer SHARED
|
||||||
|
Dll.cpp
|
||||||
|
loadimage.cpp
|
||||||
|
TenmonThumbnailProvider.cpp
|
||||||
|
../src/rawimage.h
|
||||||
|
../src/rawimage.cpp
|
||||||
|
../src/rawimage_sse.cpp)
|
||||||
|
set_target_properties(tenmonthumbnailer PROPERTIES PREFIX "")
|
||||||
|
|
||||||
|
target_compile_definitions(tenmonthumbnailer PRIVATE NO_QT)
|
||||||
|
target_include_directories(tenmonthumbnailer PRIVATE ../libXISF)
|
||||||
|
target_link_libraries(tenmonthumbnailer PRIVATE shlwapi ${FITS_LIB} XISF)
|
||||||
|
target_link_options(tenmonthumbnailer PRIVATE "-static")
|
||||||
|
else(WIN32)
|
||||||
|
qt_add_executable(tenmonthumbnailer
|
||||||
|
main.cpp
|
||||||
|
loadimage.cpp
|
||||||
|
../src/rawimage.cpp
|
||||||
|
../src/rawimage_sse.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(tenmonthumbnailer PRIVATE ${FITS_LIB} XISF)
|
||||||
|
|
||||||
|
target_include_directories(tenmonthumbnailer PRIVATE ../libXISF)
|
||||||
|
target_compile_definitions(tenmonthumbnailer PRIVATE NO_QT)
|
||||||
|
endif(WIN32)
|
||||||
|
endif(BUILD_THUMBNAILER)
|
||||||
|
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
#include <objbase.h>
|
||||||
|
#include <shlwapi.h>
|
||||||
|
#include <thumbcache.h> // For IThumbnailProvider.
|
||||||
|
#include <shlobj.h> // For SHChangeNotify
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
extern HRESULT TenmonThumbnailer_CreateInstance(REFIID riid, void **ppv);
|
||||||
|
|
||||||
|
#define SZ_CLSID_TENMONTHUMBHANDLER L"{0f3881d7-c9f0-45cb-aadb-40192aead2b4}"
|
||||||
|
#define SZ_TENMONTHUMBHANDLER L"Tenmon Thumbnail Handler"
|
||||||
|
|
||||||
|
const CLSID CLSID_TenmonThumbHandler = {0x0f3881d7, 0xc9f0, 0x45cb, {0xaa, 0xdb, 0x40, 0x19, 0x2a, 0xea, 0xd2, 0xb4}};
|
||||||
|
|
||||||
|
typedef HRESULT (*PFNCREATEINSTANCE)(REFIID riid, void **ppvObject);
|
||||||
|
struct CLASS_OBJECT_INIT
|
||||||
|
{
|
||||||
|
const CLSID *pClsid;
|
||||||
|
PFNCREATEINSTANCE pfnCreate;
|
||||||
|
};
|
||||||
|
|
||||||
|
// add classes supported by this module here
|
||||||
|
const CLASS_OBJECT_INIT c_rgClassObjectInit[] =
|
||||||
|
{
|
||||||
|
{ &CLSID_TenmonThumbHandler, TenmonThumbnailer_CreateInstance }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
long g_cRefModule = 0;
|
||||||
|
|
||||||
|
// Handle the the DLL's module
|
||||||
|
HINSTANCE g_hInst = NULL;
|
||||||
|
|
||||||
|
// Standard DLL functions
|
||||||
|
STDAPI_(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void *)
|
||||||
|
{
|
||||||
|
if (dwReason == DLL_PROCESS_ATTACH)
|
||||||
|
{
|
||||||
|
g_hInst = hInstance;
|
||||||
|
DisableThreadLibraryCalls(hInstance);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
STDAPI DllCanUnloadNow()
|
||||||
|
{
|
||||||
|
// Only allow the DLL to be unloaded after all outstanding references have been released
|
||||||
|
return (g_cRefModule == 0) ? S_OK : S_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DllAddRef()
|
||||||
|
{
|
||||||
|
InterlockedIncrement(&g_cRefModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DllRelease()
|
||||||
|
{
|
||||||
|
InterlockedDecrement(&g_cRefModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CClassFactory : public IClassFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static HRESULT CreateInstance(REFCLSID clsid, const CLASS_OBJECT_INIT *pClassObjectInits, size_t cClassObjectInits, REFIID riid, void **ppv)
|
||||||
|
{
|
||||||
|
*ppv = NULL;
|
||||||
|
HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
|
||||||
|
for (size_t i = 0; i < cClassObjectInits; i++)
|
||||||
|
{
|
||||||
|
if (clsid == *pClassObjectInits[i].pClsid)
|
||||||
|
{
|
||||||
|
IClassFactory *pClassFactory = new (std::nothrow) CClassFactory(pClassObjectInits[i].pfnCreate);
|
||||||
|
hr = pClassFactory ? S_OK : E_OUTOFMEMORY;
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
{
|
||||||
|
hr = pClassFactory->QueryInterface(riid, ppv);
|
||||||
|
pClassFactory->Release();
|
||||||
|
}
|
||||||
|
break; // match found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CClassFactory(PFNCREATEINSTANCE pfnCreate) : _cRef(1), _pfnCreate(pfnCreate)
|
||||||
|
{
|
||||||
|
DllAddRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
// IUnknown
|
||||||
|
IFACEMETHODIMP QueryInterface(REFIID riid, void ** ppv)
|
||||||
|
{
|
||||||
|
static const QITAB qit[] =
|
||||||
|
{
|
||||||
|
QITABENT(CClassFactory, IClassFactory),
|
||||||
|
{ 0 }
|
||||||
|
};
|
||||||
|
return QISearch(this, qit, riid, ppv);
|
||||||
|
}
|
||||||
|
|
||||||
|
IFACEMETHODIMP_(ULONG) AddRef()
|
||||||
|
{
|
||||||
|
return InterlockedIncrement(&_cRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
IFACEMETHODIMP_(ULONG) Release()
|
||||||
|
{
|
||||||
|
long cRef = InterlockedDecrement(&_cRef);
|
||||||
|
if (cRef == 0)
|
||||||
|
{
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
return cRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IClassFactory
|
||||||
|
IFACEMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
|
||||||
|
{
|
||||||
|
return punkOuter ? CLASS_E_NOAGGREGATION : _pfnCreate(riid, ppv);
|
||||||
|
}
|
||||||
|
|
||||||
|
IFACEMETHODIMP LockServer(BOOL fLock)
|
||||||
|
{
|
||||||
|
if (fLock)
|
||||||
|
{
|
||||||
|
DllAddRef();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DllRelease();
|
||||||
|
}
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
~CClassFactory()
|
||||||
|
{
|
||||||
|
DllRelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
long _cRef;
|
||||||
|
PFNCREATEINSTANCE _pfnCreate;
|
||||||
|
};
|
||||||
|
|
||||||
|
STDAPI DllGetClassObject(REFCLSID clsid, REFIID riid, void **ppv)
|
||||||
|
{
|
||||||
|
return CClassFactory::CreateInstance(clsid, c_rgClassObjectInit, ARRAYSIZE(c_rgClassObjectInit), riid, ppv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A struct to hold the information required for a registry entry
|
||||||
|
|
||||||
|
struct REGISTRY_ENTRY
|
||||||
|
{
|
||||||
|
HKEY hkeyRoot;
|
||||||
|
PCWSTR pszKeyName;
|
||||||
|
PCWSTR pszValueName;
|
||||||
|
PCWSTR pszData;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Creates a registry key (if needed) and sets the default value of the key
|
||||||
|
|
||||||
|
HRESULT CreateRegKeyAndSetValue(const REGISTRY_ENTRY *pRegistryEntry)
|
||||||
|
{
|
||||||
|
HKEY hKey;
|
||||||
|
HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyExW(pRegistryEntry->hkeyRoot, pRegistryEntry->pszKeyName,
|
||||||
|
0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL));
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
{
|
||||||
|
hr = HRESULT_FROM_WIN32(RegSetValueExW(hKey, pRegistryEntry->pszValueName, 0, REG_SZ,
|
||||||
|
(LPBYTE) pRegistryEntry->pszData,
|
||||||
|
((DWORD) wcslen(pRegistryEntry->pszData) + 1) * sizeof(WCHAR)));
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
}
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Registers this COM server
|
||||||
|
//
|
||||||
|
STDAPI DllRegisterServer()
|
||||||
|
{
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
WCHAR szModuleName[MAX_PATH];
|
||||||
|
|
||||||
|
if (!GetModuleFileNameW(g_hInst, szModuleName, ARRAYSIZE(szModuleName)))
|
||||||
|
{
|
||||||
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// List of registry entries we want to create
|
||||||
|
const REGISTRY_ENTRY rgRegistryEntries[] =
|
||||||
|
{
|
||||||
|
// RootKey KeyName ValueName Data
|
||||||
|
{HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER, NULL, SZ_TENMONTHUMBHANDLER},
|
||||||
|
{HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER L"\\InProcServer32", NULL, szModuleName},
|
||||||
|
{HKEY_CURRENT_USER, L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER L"\\InProcServer32", L"ThreadingModel", L"Apartment"},
|
||||||
|
{HKEY_CURRENT_USER, L"Software\\Classes\\.xisf\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", NULL, SZ_CLSID_TENMONTHUMBHANDLER},
|
||||||
|
{HKEY_CURRENT_USER, L"Software\\Classes\\.fits\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", NULL, SZ_CLSID_TENMONTHUMBHANDLER},
|
||||||
|
{HKEY_CURRENT_USER, L"Software\\Classes\\.fit\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}", NULL, SZ_CLSID_TENMONTHUMBHANDLER},
|
||||||
|
};
|
||||||
|
|
||||||
|
hr = S_OK;
|
||||||
|
for (int i = 0; i < ARRAYSIZE(rgRegistryEntries) && SUCCEEDED(hr); i++)
|
||||||
|
{
|
||||||
|
hr = CreateRegKeyAndSetValue(&rgRegistryEntries[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
{
|
||||||
|
// This tells the shell to invalidate the thumbnail cache. This is important because any .xisf files
|
||||||
|
// viewed before registering this handler would otherwise show cached blank thumbnails.
|
||||||
|
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
|
||||||
|
}
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Unregisters this COM server
|
||||||
|
//
|
||||||
|
STDAPI DllUnregisterServer()
|
||||||
|
{
|
||||||
|
HRESULT hr = S_OK;
|
||||||
|
|
||||||
|
const PCWSTR rgpszKeys[] =
|
||||||
|
{
|
||||||
|
L"Software\\Classes\\CLSID\\" SZ_CLSID_TENMONTHUMBHANDLER,
|
||||||
|
L"Software\\Classes\\.xisf\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"
|
||||||
|
L"Software\\Classes\\.fits\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"
|
||||||
|
L"Software\\Classes\\.fit\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete the registry entries
|
||||||
|
for (int i = 0; i < ARRAYSIZE(rgpszKeys) && SUCCEEDED(hr); i++)
|
||||||
|
{
|
||||||
|
hr = HRESULT_FROM_WIN32(RegDeleteTreeW(HKEY_CURRENT_USER, rgpszKeys[i]));
|
||||||
|
if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
|
||||||
|
{
|
||||||
|
// If the registry entry has already been deleted, say S_OK.
|
||||||
|
hr = S_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
#include <shlwapi.h>
|
||||||
|
#include <thumbcache.h> // For IThumbnailProvider.
|
||||||
|
#include <new>
|
||||||
|
#include "libxisf.h"
|
||||||
|
#include "../src/rawimage.h"
|
||||||
|
|
||||||
|
bool loadXISF(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
|
||||||
|
bool loadFITS(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
|
||||||
|
|
||||||
|
void RawImageToHTBITMAP(std::shared_ptr<RawImage> &rawImage, HBITMAP *hbmp, UINT thumbSize)
|
||||||
|
{
|
||||||
|
rawImage->calcStats();
|
||||||
|
|
||||||
|
DWORD thre = 20;
|
||||||
|
DWORD dataSize = 4;
|
||||||
|
HRESULT hr = HRESULT_FROM_WIN32(RegGetValueW(HKEY_CURRENT_USER, L"SOFTWARE\\nou\\Tenmon\\settings", L"thumbnailstretchthreshold", RRF_RT_DWORD, NULL, &thre, &dataSize));
|
||||||
|
|
||||||
|
float thref = 0.1f;
|
||||||
|
if(hr == S_OK)
|
||||||
|
thref = thre / 100.0f;
|
||||||
|
|
||||||
|
if(rawImage->imageStats().m_median[0] < rawImage->norm() * thref)
|
||||||
|
{
|
||||||
|
//OutputDebugStringA("Stretch image");
|
||||||
|
MTFParam params = rawImage->calcMTFParams();
|
||||||
|
rawImage->applySTF(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT w = rawImage->width();
|
||||||
|
UINT h = rawImage->height();
|
||||||
|
|
||||||
|
UINT cw = thumbSize;
|
||||||
|
UINT ch = thumbSize;
|
||||||
|
if (w > h)
|
||||||
|
ch = h * thumbSize / w;
|
||||||
|
else
|
||||||
|
cw = w * thumbSize / h;
|
||||||
|
|
||||||
|
|
||||||
|
rawImage->resize(cw, ch);
|
||||||
|
rawImage->convertToType(RawImage::UINT8);
|
||||||
|
|
||||||
|
BITMAPINFO bmi = {};
|
||||||
|
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
|
||||||
|
bmi.bmiHeader.biWidth = cw;
|
||||||
|
bmi.bmiHeader.biHeight = -static_cast<LONG>(ch);
|
||||||
|
bmi.bmiHeader.biPlanes = 1;
|
||||||
|
bmi.bmiHeader.biBitCount = 32;
|
||||||
|
bmi.bmiHeader.biCompression = BI_RGB;
|
||||||
|
UINT lw = cw * 4;
|
||||||
|
|
||||||
|
BYTE *pBits;
|
||||||
|
HBITMAP bmp = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, reinterpret_cast<void **>(&pBits), NULL, 0);
|
||||||
|
|
||||||
|
const unsigned char *p = (const unsigned char*)rawImage->data();
|
||||||
|
const unsigned short *ps = (const unsigned short*)rawImage->data();
|
||||||
|
if(rawImage->channels() == 1)
|
||||||
|
{
|
||||||
|
for(UINT y = 0; y < ch; y++)
|
||||||
|
{
|
||||||
|
for(UINT x = 0; x < cw; x++)
|
||||||
|
{
|
||||||
|
pBits[(y * lw) + x * 4 + 0] = p[y * cw + x];
|
||||||
|
pBits[(y * lw) + x * 4 + 1] = p[y * cw + x];
|
||||||
|
pBits[(y * lw) + x * 4 + 2] = p[y * cw + x];
|
||||||
|
pBits[(y * lw) + x * 4 + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(UINT y = 0; y < ch; y++)
|
||||||
|
{
|
||||||
|
for(UINT x = 0; x < cw; x++)
|
||||||
|
{
|
||||||
|
pBits[(y * lw) + x * 4 + 0] = p[y * cw * 4 + x * 4 + 2];
|
||||||
|
pBits[(y * lw) + x * 4 + 1] = p[y * cw * 4 + x * 4 + 1];
|
||||||
|
pBits[(y * lw) + x * 4 + 2] = p[y * cw * 4 + x * 4 + 0];
|
||||||
|
pBits[(y * lw) + x * 4 + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*hbmp = bmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TenmonThumbProvider : public IInitializeWithStream,
|
||||||
|
public IThumbnailProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TenmonThumbProvider() : _cRef(1), _pStream(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~TenmonThumbProvider()
|
||||||
|
{
|
||||||
|
if (_pStream)
|
||||||
|
{
|
||||||
|
_pStream->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IUnknown
|
||||||
|
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
|
||||||
|
{
|
||||||
|
static const QITAB qit[] =
|
||||||
|
{
|
||||||
|
QITABENT(TenmonThumbProvider, IInitializeWithStream),
|
||||||
|
QITABENT(TenmonThumbProvider, IThumbnailProvider),
|
||||||
|
{ 0 },
|
||||||
|
};
|
||||||
|
return QISearch(this, qit, riid, ppv);
|
||||||
|
}
|
||||||
|
|
||||||
|
IFACEMETHODIMP_(ULONG) AddRef()
|
||||||
|
{
|
||||||
|
return InterlockedIncrement(&_cRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
IFACEMETHODIMP_(ULONG) Release()
|
||||||
|
{
|
||||||
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
||||||
|
if (!cRef)
|
||||||
|
{
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
return cRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IInitializeWithStream
|
||||||
|
IFACEMETHODIMP Initialize(IStream *pStream, DWORD grfMode);
|
||||||
|
|
||||||
|
// IThumbnailProvider
|
||||||
|
IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
long _cRef;
|
||||||
|
IStream *_pStream; // provided during initialization.
|
||||||
|
};
|
||||||
|
|
||||||
|
HRESULT TenmonThumbnailer_CreateInstance(REFIID riid, void **ppv)
|
||||||
|
{
|
||||||
|
TenmonThumbProvider *pNew = new (std::nothrow) TenmonThumbProvider();
|
||||||
|
HRESULT hr = pNew ? S_OK : E_OUTOFMEMORY;
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
{
|
||||||
|
hr = pNew->QueryInterface(riid, ppv);
|
||||||
|
pNew->Release();
|
||||||
|
}
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IInitializeWithStream
|
||||||
|
IFACEMETHODIMP TenmonThumbProvider::Initialize(IStream *pStream, DWORD)
|
||||||
|
{
|
||||||
|
HRESULT hr = E_UNEXPECTED; // can only be inited once
|
||||||
|
if (_pStream == NULL)
|
||||||
|
{
|
||||||
|
// take a reference to the stream if we have not been inited yet
|
||||||
|
hr = pStream->QueryInterface(&_pStream);
|
||||||
|
}
|
||||||
|
return hr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IThumbnailProvider
|
||||||
|
IFACEMETHODIMP TenmonThumbProvider::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha)
|
||||||
|
{
|
||||||
|
LibXISF::ByteArray data;
|
||||||
|
ULONG readSize = 0;
|
||||||
|
ULONG read;
|
||||||
|
data.resize(1024*1024);
|
||||||
|
|
||||||
|
while(_pStream->Read(data.data() + readSize, data.size() - readSize, &read) == S_OK)
|
||||||
|
{
|
||||||
|
readSize += read;
|
||||||
|
data.resize(data.size() + 1024*1024);
|
||||||
|
}
|
||||||
|
readSize += read;
|
||||||
|
|
||||||
|
*pdwAlpha = WTSAT_RGB;
|
||||||
|
|
||||||
|
data.resize(readSize);
|
||||||
|
|
||||||
|
std::shared_ptr<RawImage> rawImage;
|
||||||
|
if(data[0] == 'X' && data[1] == 'I' && data[2] == 'S' && data[3] == 'F')
|
||||||
|
{
|
||||||
|
if(!loadXISF(data, rawImage))
|
||||||
|
return E_FAIL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!loadFITS(data, rawImage))
|
||||||
|
return E_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
RawImageToHTBITMAP(rawImage, phbmp, cx);
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#include "genthumbnail.h"
|
||||||
|
#include "../src/rawimage.h"
|
||||||
|
#include "../src/loadimage.h"
|
||||||
|
|
||||||
|
int generateThumbnail(const QString &input, const QString &output, uint32_t size)
|
||||||
|
{
|
||||||
|
ImageInfoData info;
|
||||||
|
std::shared_ptr<RawImage> rawImage;
|
||||||
|
if(!loadImage(input, info, rawImage, 0))
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
if(!rawImage)
|
||||||
|
return 3;
|
||||||
|
|
||||||
|
QSize rect(rawImage->width(), rawImage->height());
|
||||||
|
rect.scale(size, size, Qt::KeepAspectRatio);
|
||||||
|
rawImage->calcStats();
|
||||||
|
rawImage->resize(rect.width(), rect.height());
|
||||||
|
if(rawImage->imageStats().m_median[0] < rawImage->norm() * 0.2f)
|
||||||
|
{
|
||||||
|
MTFParam mtfParams = rawImage->calcMTFParams(true);
|
||||||
|
rawImage->applySTF(mtfParams);
|
||||||
|
}
|
||||||
|
rawImage->convertToType(RawImage::UINT8);
|
||||||
|
|
||||||
|
QImage img;
|
||||||
|
if(rawImage->channels() == 1)
|
||||||
|
img = QImage((const uchar*)rawImage->data(), rawImage->width(), rawImage->height(), rawImage->widthBytes(), QImage::Format_Grayscale8);
|
||||||
|
else
|
||||||
|
img = QImage((const uchar*)rawImage->data(), rawImage->width(), rawImage->height(), rawImage->widthBytes(), QImage::Format_RGBA8888);
|
||||||
|
|
||||||
|
if(!img.save(output, "png"))
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef GENTHUMBNAIL_H
|
||||||
|
#define GENTHUMBNAIL_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
int generateThumbnail(const QString &input, const QString &output, uint32_t size);
|
||||||
|
|
||||||
|
#endif // GENTHUMBNAIL_H
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
#include "libxisf.h"
|
||||||
|
#include "../src/rawimage.h"
|
||||||
|
#ifdef WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
#include <fitsio2.h>
|
||||||
|
|
||||||
|
bool OpenGLES = false;
|
||||||
|
|
||||||
|
bool loadXISF(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LibXISF::XISFReader xisf;
|
||||||
|
xisf.open(data);
|
||||||
|
|
||||||
|
const LibXISF::Image &xisfImage = xisf.getImage(0);
|
||||||
|
|
||||||
|
RawImage::DataType type;
|
||||||
|
switch(xisfImage.sampleFormat())
|
||||||
|
{
|
||||||
|
case LibXISF::Image::UInt8: type = RawImage::UINT8; break;
|
||||||
|
case LibXISF::Image::UInt16: type = RawImage::UINT16; break;
|
||||||
|
case LibXISF::Image::UInt32: type = RawImage::UINT32; break;
|
||||||
|
case LibXISF::Image::Float32: type = RawImage::FLOAT32; break;
|
||||||
|
case LibXISF::Image::Float64: type = RawImage::FLOAT64; break;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LibXISF::Image tmpImage = xisfImage;
|
||||||
|
tmpImage.convertPixelStorageTo(LibXISF::Image::Planar);
|
||||||
|
|
||||||
|
if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray)
|
||||||
|
{
|
||||||
|
rawImage = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
|
||||||
|
std::memcpy(rawImage->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
|
||||||
|
}
|
||||||
|
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
|
||||||
|
{
|
||||||
|
rawImage = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (LibXISF::Error &err)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
char text[1024];
|
||||||
|
sprintf_s(text, 1000, "Failed to open XISF image %s", err.what());
|
||||||
|
OutputDebugStringA(text);
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadFITS(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage)
|
||||||
|
{
|
||||||
|
fitsfile *file;
|
||||||
|
|
||||||
|
int status = 0;
|
||||||
|
int hdutype = -1;
|
||||||
|
int num = 0;
|
||||||
|
long naxes[3] = {0};
|
||||||
|
|
||||||
|
auto checkError = [&status]()
|
||||||
|
{
|
||||||
|
char err[100];
|
||||||
|
fits_get_errstatus(status, err);
|
||||||
|
#ifdef WIN32
|
||||||
|
char text[1000];
|
||||||
|
sprintf_s(text, 1000, "Failed to load FITS file %s", err);
|
||||||
|
OutputDebugStringA(text);
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const void *dataPtr = data.data();
|
||||||
|
size_t size = data.size();
|
||||||
|
fits_open_memfile(&file, "file.fits", READONLY, (void**)&dataPtr, &size, 0, nullptr, &status);
|
||||||
|
if(status)return checkError();
|
||||||
|
fits_get_num_hdus(file, &num, &status);
|
||||||
|
if(status)return checkError();
|
||||||
|
|
||||||
|
int imgtype;
|
||||||
|
int naxis;
|
||||||
|
for(int i=1; i <= num; i++)
|
||||||
|
{
|
||||||
|
fits_movabs_hdu(file, i, &hdutype, &status);if(status)return checkError();
|
||||||
|
if(hdutype == IMAGE_HDU)
|
||||||
|
{
|
||||||
|
naxes[0] = naxes[1] = naxes[2] = 0;
|
||||||
|
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);if(status)return checkError();
|
||||||
|
fits_get_img_equivtype(file, &imgtype, &status);if(status)return checkError();
|
||||||
|
|
||||||
|
if(hdutype == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
|
||||||
|
{
|
||||||
|
RawImage::DataType type;
|
||||||
|
int fitstype;
|
||||||
|
long fpixel[3] = {1,1,1};
|
||||||
|
switch(imgtype)
|
||||||
|
{
|
||||||
|
case BYTE_IMG:
|
||||||
|
type = RawImage::UINT8;
|
||||||
|
fitstype = TBYTE;
|
||||||
|
break;
|
||||||
|
case SHORT_IMG:
|
||||||
|
type = RawImage::UINT16;
|
||||||
|
fitstype = TSHORT;
|
||||||
|
break;
|
||||||
|
case USHORT_IMG:
|
||||||
|
type = RawImage::UINT16;
|
||||||
|
fitstype = TUSHORT;
|
||||||
|
break;
|
||||||
|
case ULONG_IMG:
|
||||||
|
type = RawImage::UINT32;
|
||||||
|
fitstype = TUINT;
|
||||||
|
break;
|
||||||
|
case FLOAT_IMG:
|
||||||
|
type = RawImage::FLOAT32;
|
||||||
|
fitstype = TFLOAT;
|
||||||
|
break;
|
||||||
|
case DOUBLE_IMG:
|
||||||
|
type = RawImage::FLOAT64;
|
||||||
|
fitstype = TDOUBLE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = naxes[0]*naxes[1];
|
||||||
|
size_t w = naxes[0];
|
||||||
|
size_t h = naxes[1];
|
||||||
|
|
||||||
|
RawImage img(w, h, naxis == 2 ? 1 : naxes[2], type);
|
||||||
|
uint8_t *data = static_cast<uint8_t*>(img.data());
|
||||||
|
for (int i=1; i==1 || i<=naxes[2]; i++)
|
||||||
|
{
|
||||||
|
fpixel[2] = i;
|
||||||
|
fits_read_pix(file, fitstype, fpixel, size, NULL, data + img.size() * RawImage::typeSize(type) * (i-1), NULL, &status);
|
||||||
|
if(status)return checkError();
|
||||||
|
}
|
||||||
|
if(fitstype == TSHORT)
|
||||||
|
{
|
||||||
|
uint16_t *s = static_cast<uint16_t*>(img.data());
|
||||||
|
size_t size = img.size() * img.channels();
|
||||||
|
for(size_t i=0; i<size; i++)
|
||||||
|
s[i] -= INT16_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(img.channels() == 1)
|
||||||
|
rawImage = std::make_shared<RawImage>(std::move(img));
|
||||||
|
else
|
||||||
|
rawImage = RawImage::fromPlanar(img);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include "../src/rawimage.h"
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include "stb_image_write.h"
|
||||||
|
|
||||||
|
bool loadXISF(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
|
||||||
|
bool loadFITS(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
std::vector<std::string> args;
|
||||||
|
for(int i=0; i<argc; i++)
|
||||||
|
args.push_back(argv[i]);
|
||||||
|
|
||||||
|
if(args.size() < 3)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
std::string input = args[1];
|
||||||
|
std::string output = args[2];
|
||||||
|
|
||||||
|
std::shared_ptr<RawImage> rawImage;
|
||||||
|
|
||||||
|
LibXISF::ByteArray data;
|
||||||
|
std::ifstream fr;
|
||||||
|
fr.open(input, std::ios_base::in | std::ios_base::binary);
|
||||||
|
if(!fr.is_open())
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
fr.seekg(0, std::ios_base::end);
|
||||||
|
size_t len = fr.tellg();
|
||||||
|
fr.seekg(0, std::ios_base::beg);
|
||||||
|
data.resize(len);
|
||||||
|
fr.read(data.data(), len);
|
||||||
|
if(fr.bad())
|
||||||
|
return 3;
|
||||||
|
|
||||||
|
if(input.find(".xisf") != std::string::npos)
|
||||||
|
{
|
||||||
|
if(!loadXISF(data, rawImage))
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!loadFITS(data, rawImage))
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!rawImage)
|
||||||
|
return 5;
|
||||||
|
|
||||||
|
uint32_t thumbSize = 256;
|
||||||
|
|
||||||
|
uint32_t w = rawImage->width();
|
||||||
|
uint32_t h = rawImage->height();
|
||||||
|
|
||||||
|
uint32_t cw = thumbSize;
|
||||||
|
uint32_t ch = thumbSize;
|
||||||
|
if (w > h)
|
||||||
|
ch = h * thumbSize / w;
|
||||||
|
else
|
||||||
|
cw = w * thumbSize / h;
|
||||||
|
|
||||||
|
rawImage->calcStats();
|
||||||
|
rawImage->resize(cw, ch);
|
||||||
|
if(rawImage->imageStats().m_median[0] < rawImage->norm() * 0.1f)
|
||||||
|
{
|
||||||
|
MTFParam mtfParams = rawImage->calcMTFParams(true);
|
||||||
|
rawImage->applySTF(mtfParams);
|
||||||
|
}
|
||||||
|
rawImage->convertToType(RawImage::UINT8);
|
||||||
|
|
||||||
|
if(rawImage->channels() == 1)
|
||||||
|
stbi_write_png(output.c_str(), cw, ch, 1, rawImage->data(), rawImage->widthBytes());
|
||||||
|
else
|
||||||
|
stbi_write_png(output.c_str(), cw, ch, 4, rawImage->data(), rawImage->widthBytes());
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user