Compare commits

...

63 Commits

Author SHA1 Message Date
nou 0ff2001797 Handle MAX_PATH every where 2025-01-26 16:00:00 +01:00
nou fc36024eee Remove deprecated QSqlQuery usage 2025-01-19 15:49:31 +01:00
nou 3cda53f26c Do not attempt open not existing file 2025-01-19 15:11:32 +01:00
nou 58d18cc28a Remove unused function 2025-01-19 15:11:14 +01:00
nou 2b96da60de Do not use global thread pool 2025-01-12 10:59:12 +01:00
nou 236f66ed2f Add solver profile to script engine 2024-12-27 23:20:51 +01:00
nou a86c100e69 Fix issue with Qt 6.8 2024-12-22 12:19:34 +01:00
nou 45ee9b7258 Fix half pixel offset and add filtering in sw rendering
Signed-off-by: Dušan Poizl <nou.spiro@gmail.com>
2024-12-05 16:02:46 +01:00
nou be1e65251d Support really big images 50000px 2024-11-30 22:03:58 +01:00
nou 9b7837e9fb SW rendering when image is too big for texture 2024-11-27 20:21:57 +01:00
nou 4afa940886 Update metainfo 2024-11-16 22:58:34 +01:00
nou d1344d2dc8 Add support for uint32 and double in boxResample 2024-11-15 23:29:22 +01:00
nou 24eea573e6 Handle all data types when converting to QImage 2024-10-29 19:43:16 +01:00
nou 8f7f527732 Add RawImage::convertToType() 2024-10-29 19:42:36 +01:00
nou 3635ac00cb Fix alpha channel in fromPlanarSSE 2024-10-29 19:39:37 +01:00
nou eba9110933 Remove dead code 2024-10-29 15:32:06 +01:00
nou 464207beb1 Apply index folder directory change immediatly 2024-10-24 20:15:06 +02:00
nou 4aeff61c44 Add TIFF as valid format for script convert 2024-10-13 19:58:13 +02:00
nou 790c836bbd MXE build 2024-10-13 19:58:12 +02:00
nou 62616898ed Forgot to add fts and fz to one place 2024-10-13 19:57:21 +02:00
nou e216af6a6d Better handling of missing and overwrite files 2024-10-13 19:57:21 +02:00
nou e0d6f417a0 fixup! Fix compiling without stellarsolver 2024-10-04 20:51:53 +02:00
nou 3c5fef988e Fix compiling without stellarsolver 2024-10-02 15:25:32 +02:00
nou 6c42315f87 Update metainfo 2024-10-02 11:58:39 +02:00
nou 06b3dbc1bb Update help and translation 2024-10-02 11:52:35 +02:00
nou 258553a6bb Change url for downloading 2024-10-02 10:08:50 +02:00
nou 9c40ce2daa Add open marked files 2024-10-02 00:10:07 +02:00
nou 9f4c4c8bdc Refining platesolving 2024-10-01 17:37:34 +02:00
nou da1aa4c6fc Updating FITS header 2024-09-30 21:19:23 +02:00
nou a43f12565d Add FTS and FZ as FITS file suffix 2024-09-30 19:56:53 +02:00
nou 32973c54ce Adding platesolving 2024-09-30 18:39:35 +02:00
nou dccb2e88da Working solver 2024-09-20 14:34:21 +02:00
nou c8898387fe Http download of index files 2024-09-19 15:28:57 +02:00
nou dfe31b6350 In live mode reload immediatly 2024-09-19 13:40:41 +02:00
nou 553e72a5ce Add check for new version 2024-09-19 13:40:41 +02:00
nou 12901c9a47 Fix that comment in FITS record contained value 2024-09-17 23:06:16 +02:00
nou da79197376 Initial stellarsolver implementation 2024-09-17 23:05:27 +02:00
nou 30960033c5 Refractor ImageScrollArea 2024-09-17 12:31:55 +02:00
nou efd3ff35f3 Fix unpack aligment 2024-09-14 23:12:50 +02:00
nou 52bcb10da1 Proper LCMS2 link don't use OpenGL ES on MacOS 2024-08-27 15:27:27 +02:00
nou 9adfbde512 Remember auto stretch on load 2024-08-27 13:43:17 +02:00
nou e38de510a0 Open also file:// 2024-08-27 13:17:53 +02:00
nou 5e18c591f7 Add support for FLOAT16 image downscale 2024-08-27 11:39:47 +02:00
nou d6e257e201 Use flatpak trash portal instad of g_file_trash 2024-08-26 11:43:57 +02:00
nou 2c7a7d473f Fix output path browse dialog path 2024-08-26 10:19:48 +02:00
nou 87d7bd2d9f Add delay to directory reload 2024-08-25 22:20:41 +02:00
nou 79dd7d91eb Fix fromPlanaerSSE 2024-08-25 17:55:59 +02:00
nou 21b4e0934c Set GL_UNPACK_ALIGNMENT 2024-08-25 17:55:47 +02:00
nou 0239aba165 Debayer fix on OpenGL ES 2024-08-25 14:39:07 +02:00
nou 1b08242433 Use OpenGL ES on ARM by default 2024-08-25 12:49:14 +02:00
nou a56e8a2c27 Fix bug with thumbnails on OpenGL ES 2024-08-25 12:41:12 +02:00
nou bf360c1ae1 Fix thumbnails draw 2024-08-24 18:17:41 +02:00
nou 1ec3a6cffd Revert change in app id 2024-08-24 17:49:01 +02:00
nou 100f47746c Fix path 2024-08-24 17:14:10 +02:00
nou 02bac0c850 Fix compiler error with float16 2024-08-24 17:14:02 +02:00
nou bc29dc7d34 Use lcms2 for color profiles 2024-08-24 16:37:06 +02:00
nou ff5053b626 Added missing case FLOAT16 2024-08-22 19:10:26 +02:00
nou 511802bdbd Usable OpenGL ES 2024-08-22 17:54:26 +02:00
nou dd16a02045 Preparation for OpenGL ES 2024-08-20 17:53:30 +02:00
nou 7ed38cf6d7 Additional missing dependencies 2024-08-20 17:51:52 +02:00
nou 8c6b451564 Suggestion by Der_Pit 2024-08-17 19:56:01 +02:00
nou d288810d5d Fix saving image 2024-08-16 15:16:37 +02:00
nou fb66e82428 Workaround for huge PCL keywords 2024-08-16 15:04:25 +02:00
59 changed files with 5512 additions and 1295 deletions
+34 -16
View File
@@ -23,7 +23,9 @@ find_library(GSLCBLAS_LIB gslcblas REQUIRED)
find_library(EXIF_LIB exif REQUIRED)
find_library(FITS_LIB cfitsio REQUIRED)
find_library(RAW_LIB NAMES raw_r REQUIRED)
find_library(WCS_LIB wcs wcslib PATHS REQUIRED)
find_library(WCS_LIB wcs wcslib REQUIRED)
find_library(LCMS2_LIB lcms2 REQUIRED)
find_library(STELLARSOLVER_LIB stellarsolver)
add_subdirectory(libXISF)
@@ -35,10 +37,11 @@ set(TENMON_SRC
delete.cpp
filesystemwidget.cpp filesystemwidget.h
histogram.cpp histogram.h
httpdownloader.h httpdownloader.cpp
imageinfo.cpp imageinfo.h
imageringlist.cpp imageringlist.h
imagescrollarea.cpp
imagescrollareagl.cpp imagescrollareagl.h
imagescrollarea.cpp imagescrollarea.h
imagewidget.h imagewidget.cpp
loadrunable.cpp loadrunable.h
main.cpp
mainwindow.cpp mainwindow.h
@@ -53,12 +56,6 @@ set(TENMON_SRC
stretchtoolbar.cpp stretchtoolbar.h
)
option(COLOR_MANAGMENT "Enable sRGB framebuffer support for gamma correct images and color profiles support" ON)
if(COLOR_MANAGMENT)
add_compile_definitions("COLOR_MANAGMENT")
endif(COLOR_MANAGMENT)
qt_add_resources(TENMON_SRC resources/resources.qrc)
qt_add_resources(TENMON_SRC shaders/shaders.qrc)
qt_add_resources(TENMON_SRC scripts/scripts.qrc)
@@ -72,8 +69,6 @@ elseif(APPLE)
else()
set(tenmon_ICON "")
find_package(Qt6 COMPONENTS DBus REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_search_module(GIO REQUIRED gio-2.0)
endif()
qt_add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC})
@@ -81,15 +76,33 @@ qt_add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC})
find_path(FITS_INCLUDE fitsio2.h PATH_SUFFIXES cfitsio REQUIRED)
target_include_directories(tenmon PRIVATE ${FITS_INCLUDE} ${CMAKE_BINARY_DIR} ${libXISF_SOURCE_DIR})
if(UNIX AND NOT APPLE)
target_include_directories(tenmon PRIVATE ${GIO_INCLUDE_DIRS})
endif()
option(COLOR_MANAGMENT "Enable sRGB framebuffer support for gamma correct images and color profiles support" ON)
if(COLOR_MANAGMENT)
target_compile_definitions(tenmon PRIVATE "COLOR_MANAGMENT")
endif(COLOR_MANAGMENT)
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} XISF)
find_path(STELLARSOLVER_INCLUDE stellarsolver.h PATH_SUFFIXES libstellarsolver)
if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
target_include_directories(tenmon PRIVATE ${STELLARSOLVER_INCLUDE})
if(MXE)
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB} ${GSL_LIB} ${GSLCBLAS_LIB} boost_regex-mt-x64)
else(MXE)
target_link_libraries(tenmon PRIVATE ${STELLARSOLVER_LIB})
endif(MXE)
target_compile_definitions(tenmon PRIVATE "PLATESOLVER")
target_sources(tenmon PRIVATE
solver.cpp solver.h
platesolving.cpp platesolving.h platesolving.ui
platesolvingsettings.cpp platesolvingsettings.h platesolvingsettings.ui
)
message(STATUS "Found stellarsolver ${STELLARSOLVER_INCLUDE} ${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)
if(APPLE)
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
elseif(UNIX)
target_link_libraries(tenmon PRIVATE Qt6::DBus ${GIO_LDFLAGS})
target_link_libraries(tenmon PRIVATE Qt6::DBus)
endif(APPLE)
if(LIBRAW_STATIC)
@@ -97,6 +110,11 @@ if(LIBRAW_STATIC)
target_link_libraries(tenmon PRIVATE jasper)
endif()
option(FLATPAK "Flatpak build" OFF)
if(FLATPAK)
target_compile_definitions(tenmon PRIVATE FLATPAK)
endif(FLATPAK)
install(TARGETS tenmon BUNDLE DESTINATION .)
if(UNIX AND NOT APPLE)
include(GNUInstallDirs)
+12 -5
View File
@@ -2,23 +2,30 @@ FITS/XISF image viewer with multithreaded image loading
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
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
on OpenSUSE
sudo zypper install gsl-devel exif-devel libraw-devel wcslib-devel libqt6-qtbase-devel
sudo zypper install gsl-devel libexif-devel libraw-devel wcslib-devel qt6-base-devel qt6-qml-devel libzstd-devel
MacOS X
To compile on MacOS install XCode first. Then install homebrew in x86_64 mode
with "arch -i x86_64". Building on native ARM is not supported.
To compile on MacOS install XCode first. Then install homebrew.
homebrew install qt6 libraw cfitsio libexif libgsl wcslib
You may need to set CMAKE_PREFIX_PATH for Qt6 so CMake can find them.
Then to build run standard cmake
First run this command to get libXISF updated
git submodule update --init --recursive
Then to build run standard cmake sequence
cmake -B build -S .
cmake --build build
./build/tenmon
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
crash.
+7
View File
@@ -46,3 +46,10 @@ HelpDialog::HelpDialog(QWidget *parent) : QDialog(parent)
layout->addWidget(helpText);
}
QString getVersion()
{
QString version = GITVERSION;
version.truncate(8);
return version;
}
+2
View File
@@ -17,4 +17,6 @@ public:
HelpDialog(QWidget *parent = nullptr);
};
QString getVersion();
#endif // ABOUT_H
+29 -1
View File
@@ -116,6 +116,24 @@ Pressing Enter or clicking on <i>Filter</i> button will filter out database reco
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>
<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>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>Batch processing</h3>
@@ -155,6 +173,8 @@ There is global object called <b>core</b> that have these methods.
<li><b>getFloat(label = "", value = 0, decimals = 3)</b> show dialog box with input box to retrieve decimal value. String value passed in first argument is used as description label.
Second parameter is default value in input box. All three parameters are optional. 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".
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>
</ul>
<h4>File</h4>
@@ -182,7 +202,7 @@ In <b>files</b> array there are instances of type <b>File</b> objects that have
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>
<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" 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. <i>format</i> one of "FITS" "XISF", "JPG", "PNG", "TIFF" or "BMP". <i>params</i> object with attributes "compressionType" and "compressionLevel".
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".
It is recommended to use "+sh" variants of compression.
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.
@@ -208,6 +228,14 @@ for(file of convertedFiles)// now we can iterate over the files
<li><b>stats()</b> calculate basic images statistics and return them as object with attributes "mean", "stddev", "median", "min", "max" and "mad".
<pre>let s = file.stats();
core.log("Median value is " + s.median);</pre></li>
<li><b>solve(updateHeader)</b> this method will run plate solving on this image and will return solution in form of object with these attributes "ra" and "dec" which are center coordinates of image
"fieldWidth" and "fieldHeight" which is FOV of image in arcseconds, "orientation" is degrees east of north, "pixscale" scale of image in arcseconds per pixel,
"parity" true false value if the image was flipped in vertical direction, "raError" and "decError" deviation from starting point.
When updateHeader is set to true it update FITS header for file with this solution. Default value is false.</li>
<li><b>extractStars(hfr)</b> extract stars will run extraction of stars. When parameter hfr is set to true it will fit HFR on every star.
It return array of objects representing extracted stars. Each object in array
will have these attributes "x" and "y" pixel coordinates of center of star, "mag" relative magnitude of star, "flux" total flux, "peak" peak value of star, "HFR" half flux radius of star,
"a" and "b" semi major and minor axis of star, "theta" angle of orientation of the star, "ra" and "dec" coordinates of star, "numPixels" number of pixel occupied by the star in image.</li>
</ul>
<h4>FITSRecordModify</h4>
+11 -1
View File
@@ -133,6 +133,8 @@ Le deuxième paramètre est la valeur par défaut dans la zone de saisie. Les de
<li><b>getFloat(label = "", value = 0, decimals = 3)</b> affiche une boîte de dialogue avec une zone de saisie pour récupérer une valeur décimale. Le texte passé dans le premier argument est utilisé comme label de description.
Le deuxième paramètre est la valeur par défaut dans la zone de saisie. Les trois paramètres sont facultatifs. 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".
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>
</ul>
<h4>File</h4>
@@ -159,7 +161,7 @@ le fichier <i>C:/images/lights/red/M42_001.fits</i>, alors cette méthode renver
Ce chemin peut être relatif ou absolu. Dans le cas où le paramètre <i>newpath</i> est un chemin relatif, le "répertoire de sortie" des fenêtres de l'interface graphique est utilisé comme répertoire de base. Le paramètre <i>newpath</i> peut être un chemin absolu.
Le fichier est ensuite déplacé vers ce chemin.</li>
<li><b>convert(outpath, format, params)</b> Convertir un fichier image à partir de n'importe quel format que le programme peut ouvrir en FITS, XISF, JPEG, PNG, BMP.
Les paramètres sont : <i>outputpath</i> chemin où l'image convertie sera enregistrée. Il remplace automatiquement le suffixe en fonction du format. <i>format</i> l'un des éléments suivants : "FITS", "XISF", "JPG", "PNG" ou "BMP". <i>params</i> objet avec les attributs "compressionType" et "compressionLevel".
Les paramètres sont : <i>outputpath</i> chemin où l'image convertie sera enregistrée. Il remplace automatiquement le suffixe en fonction du format. <i>format</i> l'un des éléments suivants : "FITS", "XISF", "JPG", "PNG", "TIFF" ou "BMP". <i>params</i> objet avec les attributs "compressionType" et "compressionLevel".
Les valeurs valides pour compressionType sont "gzip" ou "rice" lors de la conversion en FITS. Lors de la conversion en XISF, compressionType peut être "zlib", "lz4", "lz4hc", "zstd", "zlib+sh", "lz4+sh", "lz4hc+sh", "zstd+sh".
Il est recommandé d'utiliser les variantes de compression "+sh".
Le format XISF accepte également les "compressionLevel" dans la plage 0-100, où zéro est la compression la plus rapide et 100 la plus lente. Si vous omettez cet attribut ou le définissez sur -1, le niveau de compression par défaut sera utilisé.
@@ -185,6 +187,14 @@ for(file of convertedFiles)// now we can iterate over the files
<li><b>stats()</b> calculer les statistiques d'images de base et les renvoyer sous forme d'objet avec des attributs "mean", "stddev", "median", "min", "max" et "mad".
<pre>let s = file.stats();
core.log("Median value is " + s.median);</pre></li>
<li><b>solve(updateHeader)</b> this method will run plate solving on this image and will return solution in form of object with these attributes "ra" and "dec" which are center coordinates of image
"fieldWidth" and "fieldHeight" which is FOV of image in arcseconds, "orientation" is degrees east of north, "pixscale" scale of image in arcseconds per pixel,
"parity" true false value if the image was flipped in vertical direction, "raError" and "decError" deviation from starting point.
When updateHeader is set to true it update FITS header for file with this solution. Default value is false.</li>
<li><b>extractStars(hfr)</b> extract stars will run extraction of stars. When parameter hfr is set to true it will fit HFR on every star.
It return array of objects representing extracted stars. Each object in array
will have these attributes "x" and "y" pixel coordinates of center of star, "mag" relative magnitude of star, "flux" total flux, "peak" peak value of star, "HFR" half flux radius of star,
"a" and "b" semi major and minor axis of star, "theta" angle of orientation of the star, "ra" and "dec" coordinates of star, "numPixels" number of pixel occupied by the star in image.</li>
</ul>
<h4>FITSRecordModify</h4>
+11 -1
View File
@@ -122,6 +122,8 @@ V skripte je dostupný globálny objekt nazvaný <b>core</b> ktorý má nasledov
<li><b>getFloat(label = "", value = 0, decimals = 3)</b> ukáže diálog pre získanie reálneho čísla. Prvý parameter je textový popis. Druhý parameter je východzia hodnota. Tretí parameter je počet desatinných miest.
Obydva parametre sú voliteľné. Vracia zadané číslo 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".
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>
</ul>
<h4>File</h4>
@@ -149,7 +151,7 @@ V poli <b>files</b> sú inštancie objektu typu <b>File</b> ktorý ma nasledovn
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>
<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" 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. <i>format</i> one of "FITS" "XISF", "JPG", "PNG", "TIFF" or "BMP". <i>params</i> object with attributes "compressionType" and "compressionLevel".
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".
It is recommended to use "+sh" variants of compression.
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.
@@ -175,6 +177,14 @@ for(file of convertedFiles)// now we can iterate over the files
<li><b>stats()</b> calculate basic images statistics and return them as object with attributes "mean", "stddev", "median", "min", "max" and "mad".
<pre>let s = file.stats();
core.log("Median value is " + s.median);</pre></li>
<li><b>solve(updateHeader)</b> this method will run plate solving on this image and will return solution in form of object with these attributes "ra" and "dec" which are center coordinates of image
"fieldWidth" and "fieldHeight" which is FOV of image in arcseconds, "orientation" is degrees east of north, "pixscale" scale of image in arcseconds per pixel,
"parity" true false value if the image was flipped in vertical direction, "raError" and "decError" deviation from starting point.
When updateHeader is set to true it update FITS header for file with this solution. Default value is false.</li>
<li><b>extractStars(hfr)</b> extract stars will run extraction of stars. When parameter hfr is set to true it will fit HFR on every star.
It return array of objects representing extracted stars. Each object in array
will have these attributes "x" and "y" pixel coordinates of center of star, "mag" relative magnitude of star, "flux" total flux, "peak" peak value of star, "HFR" half flux radius of star,
"a" and "b" semi major and minor axis of star, "theta" angle of orientation of the star, "ra" and "dec" coordinates of star, "numPixels" number of pixel occupied by the star in image.</li>
</ul>
<h4>FITSRecordModify</h4>
+1 -1
View File
@@ -172,7 +172,7 @@ void BatchProcessing::removeAllPaths()
void BatchProcessing::browse()
{
QString output = QFileDialog::getExistingDirectory(this, tr("Select output directory"), "/home/nou/Obrázky");
QString output = QFileDialog::getExistingDirectory(this, tr("Select output directory"), _ui->outputPath->text());
if(!output.isEmpty())
_ui->outputPath->setText(output);
}
+31 -30
View File
@@ -15,32 +15,33 @@ bool Database::init(const QLatin1String &connectionName)
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir dir(path);
QSqlDatabase m_database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
QSqlDatabase database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
if(!dir.mkpath("."))
return false;
if(m_database.isValid())
if(database.isValid())
{
m_database.setDatabaseName(dir.absoluteFilePath("database2.db"));
if(m_database.open())
database.setDatabaseName(dir.absoluteFilePath("database2.db"));
if(database.open())
{
m_database.exec("PRAGMA foreign_keys = ON");
QSqlQuery query(database);
query.exec("PRAGMA foreign_keys = ON");
int version = checkVersion();
if(version == 0)
{
m_database.exec("PRAGMA user_version = 1");
m_database.exec("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE, mtime DATETIME,"
query.exec("PRAGMA user_version = 1");
query.exec("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE)");
query.exec("CREATE TABLE IF NOT EXISTS fits_files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE, mtime DATETIME,"
" minRa REAL, maxRa REAL, minDec REAL, maxDec REAL, crVal1 REAL, crVal2 REAL)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_headers (id INTEGER PRIMARY KEY AUTOINCREMENT, id_file INTEGER,"
query.exec("CREATE TABLE IF NOT EXISTS fits_headers (id INTEGER PRIMARY KEY AUTOINCREMENT, id_file INTEGER,"
"key VARCHAR(81), value VARCHAR(81), comment VARCHAR(81), FOREIGN KEY(id_file) REFERENCES fits_files(id) ON DELETE CASCADE)");
m_database.exec("CREATE INDEX IF NOT EXISTS key_value ON fits_headers(key, value)");
m_database.exec("CREATE INDEX IF NOT EXISTS id_file ON fits_headers(id_file)");
m_database.exec("CREATE INDEX IF NOT EXISTS minRa_idx ON fits_files(minRa)");
m_database.exec("CREATE INDEX IF NOT EXISTS maxRa_idx ON fits_files(maxRa)");
m_database.exec("CREATE INDEX IF NOT EXISTS minDec_idx ON fits_files(minDec)");
m_database.exec("CREATE INDEX IF NOT EXISTS maxDec_idx ON fits_files(maxDec)");
query.exec("CREATE INDEX IF NOT EXISTS key_value ON fits_headers(key, value)");
query.exec("CREATE INDEX IF NOT EXISTS id_file ON fits_headers(id_file)");
query.exec("CREATE INDEX IF NOT EXISTS minRa_idx ON fits_files(minRa)");
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 maxDec_idx ON fits_files(maxDec)");
}
else if(version > 1)
{
@@ -48,28 +49,28 @@ bool Database::init(const QLatin1String &connectionName)
return false;
}
QSqlError error = m_database.lastError();
QSqlError error = database.lastError();
if(error.type() == QSqlError::NoError)
{
m_markQuery = QSqlQuery(m_database);
m_markQuery = QSqlQuery(database);
m_markQuery.prepare("INSERT INTO files (file) VALUES (?)");
m_unmarkQuery = QSqlQuery(m_database);
m_unmarkQuery = QSqlQuery(database);
m_unmarkQuery.prepare("DELETE FROM files WHERE file = (?)");
m_isMarkedQuery = QSqlQuery(m_database);
m_isMarkedQuery = QSqlQuery(database);
m_isMarkedQuery.prepare("SELECT * FROM files WHERE file = (:name)");
m_insertFile = QSqlQuery(m_database);
m_insertFile = QSqlQuery(database);
m_insertFile.prepare("INSERT INTO fits_files (file, mtime) VALUES (?, ?)");
m_insertFileWcs = QSqlQuery(m_database);
m_insertFileWcs = QSqlQuery(database);
m_insertFileWcs.prepare("INSERT INTO fits_files (file, mtime, minRa, maxRa, minDec, maxDec, crVal1, crVal2) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
m_insertFitsHeader = QSqlQuery(m_database);
m_insertFitsHeader = QSqlQuery(database);
m_insertFitsHeader.prepare("INSERT INTO fits_headers (id_file, key, value, comment) VALUES (?, ?, ?, ?)");
m_checkFile = QSqlQuery(m_database);
m_checkFile = QSqlQuery(database);
m_checkFile.prepare("SELECT id,mtime FROM fits_files WHERE file=?");
m_headerKeywords = QSqlQuery(m_database);
m_headerKeywords = QSqlQuery(database);
m_headerKeywords.prepare("SELECT DISTINCT key FROM fits_headers ORDER BY key");
m_deleteFile = QSqlQuery(m_database);
m_deleteFile = QSqlQuery(database);
m_deleteFile.prepare("DELETE FROM fits_files WHERE id=?");
return true;
}
@@ -130,7 +131,7 @@ QStringList Database::getMarkedFiles()
void Database::clearMarkedFiles()
{
QSqlDatabase::database().exec("DELETE FROM files");
QSqlQuery query("DELETE FROM files");
}
bool Database::checkError(QSqlQuery &query)
@@ -148,13 +149,13 @@ bool Database::checkError(QSqlQuery &query)
int Database::checkVersion()
{
QSqlDatabase db = QSqlDatabase::database();
QSqlQuery query = db.exec("PRAGMA user_version");
QSqlQuery query("PRAGMA user_version");
if(query.next())
return query.value(0).toInt();
return -1;
}
static QStringList nameFilters = {"*.fit", "*.fits", "*.xisf"};
static QStringList nameFilters = {"*.fit", "*.fits", "*.fz", "*.xisf"};
static int countFiles(const QDir &dir, QStringList &scannedDirs)
{
@@ -194,10 +195,10 @@ void Database::reindex(QProgressDialog *progress)
QVariantList deleteids;
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
QSqlQuery size = database.exec("SELECT COUNT(*) FROM fits_files");
QSqlQuery size("SELECT COUNT(*) FROM fits_files", database);
size.next();
progress->setMaximum(size.value(0).toInt());
QSqlQuery files = database.exec("SELECT id,file,mtime FROM fits_files");
QSqlQuery files("SELECT id,file,mtime FROM fits_files", database);
int i = 0;
while(files.next())
{
+2 -1
View File
@@ -286,6 +286,7 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
for(int i=0; i<3; i++)
{
m_filterKeyword[i] = new QComboBox(this);
m_filterKeyword[i]->setMaximumWidth(300);
addFilterItems(m_filterKeyword[i], fitsKeywords);
@@ -374,7 +375,7 @@ bool DataBaseView::exportCSV(const QString &path)
if(!csv.open(QIODevice::WriteOnly | QIODevice::Text))
return false;
QSqlQuery sql = m_model->query();
QSqlQuery sql(m_model->query().lastQuery());
int colCount = m_model->columnCount();
QStringList header;
for(int i=0; i<colCount; i++)
+21 -11
View File
@@ -1,20 +1,30 @@
#ifdef __linux__
#ifdef FLATPAK
#define QT_NO_KEYWORDS
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusUnixFileDescriptor>
#include <QString>
#include <iostream>
#include <gio/gio.h>
#include <fcntl.h>
#include <unistd.h>
//flatpak bug prevent to use QFile::moveToTrash
bool moveToTrash(const QString &path)
{
GFile *gfile = g_file_new_for_path(path.toLocal8Bit().data());
GError *error = nullptr;
g_file_trash(gfile, nullptr, &error);
if(error)std::cerr << "failed to trash file " << error->code << " " << error->message << std::endl;
g_clear_error(&error);
g_object_unref(gfile);
return true;
QDBusConnection con = QDBusConnection::sessionBus();
QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Trash", "TrashFile");
int fd = ::open(path.toLocal8Bit().data(), O_RDWR);
if(fd >= 0)
{
QList<QVariant> args = {QVariant::fromValue(QDBusUnixFileDescriptor(fd))};
message.setArguments(args);
QDBusMessage reply = con.call(message);
::close(fd);
if(reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() && reply.arguments().first().toInt())
return true;
else
return false;
}
return false;
}
#else
+1 -1
View File
@@ -57,7 +57,7 @@ Filetree::Filetree(QWidget *parent) : QTreeView(parent)
m_rootDir = settings.value("filetree/rootDir", QDir::homePath()).toString();
m_fileSystemModel = new FileSystemModel(this);
m_fileSystemModel->setRootPath(m_rootDir);
m_fileSystemModel->setNameFilters({"*.fits", "*.fit", "*.xisf", "*.jpg", "*.jpeg", "*.png", "*.cr2", "*.nef", "*.dng"});
m_fileSystemModel->setNameFilters({"*.fits", "*.fit", "*.fz", "*.xisf", "*.jpg", "*.jpeg", "*.png", "*.cr2", "*.nef", "*.dng"});
m_fileSystemModel->setNameFilterDisables(false);
if(settings.value("filetree/showHidden", false).toBool())
m_fileSystemModel->setFilter(m_fileSystemModel->filter() | QDir::Hidden);
+2 -1
View File
@@ -14,8 +14,9 @@ class FilesystemWidget : public QWidget
public:
explicit FilesystemWidget(QAbstractItemModel *model, QWidget *parent = nullptr);
void contextMenuEvent(QContextMenuEvent *event) override;
private slots:
public slots:
void selectFile(int row);
protected slots:
void fileClicked(const QModelIndex &index, const QModelIndex &);
signals:
void fileSelected(int row);
+527
View File
@@ -0,0 +1,527 @@
#include "httpdownloader.h"
#include <QNetworkReply>
#include <QDebug>
#include <QRegularExpression>
#include <QFileInfo>
#ifdef PLATESOLVER
#include "solver.h"
#endif
// filename arcseconds range
// index-4119.fits 14002000
// index-4118.fits 10001400
// index-4117.fits 6801000
// index-4116.fits 480680
// index-4115.fits 340480
// index-4114.fits 240340
// index-4113.fits 170240
// index-4112.fits 120170
// index-4111.fits 85120
// index-4110.fits 60—85
// index-4109.fits 4260
// index-4108.fits 3042
// index-4107.fits 2230
// index-5206-*.fits 1622
// index-5205-*.fits 1116
// index-5204-*.fits 811
// index-5203-*.fits 5.68.0
// index-5202-*.fits 4.05.6
// index-5201-*.fits 2.84.0
static const QMap<QString, QByteArray> md5 = {
{"index-4107.fits.zst", "b4c3bc2b162fcb6417b2c3358dbf0543"},
{"index-4108.fits.zst", "14a54b8e0abcb58efb7a828fc8f00267"},
{"index-4109.fits.zst", "d6bce03dfbb527cc807ec360a8b4afa6"},
{"index-4110.fits.zst", "da0aded630ee4650850f5828b4289746"},
{"index-4111.fits.zst", "c11547481f97727e546b3b7c776f6394"},
{"index-4112.fits.zst", "fd3f5ad964d69c66555b2c5b6d65d426"},
{"index-4113.fits.zst", "4546e33817a161b8011e5f1321d39445"},
{"index-4114.fits.zst", "ebc815fa4d9a3fd259fe22b84796fbc4"},
{"index-4115.fits.zst", "5395b7b225ffe5329867354bc653887f"},
{"index-4116.fits.zst", "341cebc6b962cede0f27d08c3b3a4f23"},
{"index-4117.fits.zst", "e362a868ae0751d1a1e7f6b9e48a2f79"},
{"index-4118.fits.zst", "a7d38ec4b1d69c859e875c8d6ba1679b"},
{"index-4119.fits.zst", "9e07b46f4c4ca9ba536383d201e70c35"},
{"index-5201-00.fits.zst", "87255d073576674ec50959522cbbc9eb"},
{"index-5201-01.fits.zst", "b5154f26c8b2a6e143bdc11a062213ab"},
{"index-5201-02.fits.zst", "cf0b08e586fe2ce306adb370c9f113e8"},
{"index-5201-03.fits.zst", "eda457e3b3b419156b0cbdbe6c262fb7"},
{"index-5201-04.fits.zst", "e1344126047714aac771d37861da4698"},
{"index-5201-05.fits.zst", "1b2bf2fe61e883db7e65628761a934e8"},
{"index-5201-06.fits.zst", "e4338de4ae486cedd31ec24b2677fe1d"},
{"index-5201-07.fits.zst", "14665b88b4ab179d1bedd46acdc0d9bd"},
{"index-5201-08.fits.zst", "636f411a83dfcf0c02e13ad4c0fed948"},
{"index-5201-09.fits.zst", "8afe4edf38794225c1c3b23d72671d96"},
{"index-5201-10.fits.zst", "742db3b858e160f69f2d189961fdfcad"},
{"index-5201-11.fits.zst", "0ffb50923c71d269acc9c3c661d5429a"},
{"index-5201-12.fits.zst", "535eefd763e08593e775e0f4e19c69e3"},
{"index-5201-13.fits.zst", "e94426ba2275e76b11495105d780890d"},
{"index-5201-14.fits.zst", "754a22f37153773662acaea5ea34a417"},
{"index-5201-15.fits.zst", "7e399e94b7a15c2b97e14f49b3999070"},
{"index-5201-16.fits.zst", "7441074047de8bccd1c09570b122466d"},
{"index-5201-17.fits.zst", "bb7f5979b0d7963420dabfc5dd58407c"},
{"index-5201-18.fits.zst", "ca950e0190d849d709357bacce6fc1d0"},
{"index-5201-19.fits.zst", "36b84a8ac921064ad1a89f1155af7b31"},
{"index-5201-20.fits.zst", "25eeda073f427462e0064acf23a38498"},
{"index-5201-21.fits.zst", "0bd79e677363442dc7e994b2f088cd27"},
{"index-5201-22.fits.zst", "071abfb9131ca5a6cda792870f97bd8d"},
{"index-5201-23.fits.zst", "56721c1918e7ac114d43602ec6b17402"},
{"index-5201-24.fits.zst", "4409be2965dacf376b0124d8f7342c3c"},
{"index-5201-25.fits.zst", "e784c443787e6c3b3b51e7c82701b3b6"},
{"index-5201-26.fits.zst", "02e58904a47e3305dd2a2c1e754c2b56"},
{"index-5201-27.fits.zst", "f4f37044f787349dfda36e9aab07c348"},
{"index-5201-28.fits.zst", "69893cbd149173c98d496b3d62d23526"},
{"index-5201-29.fits.zst", "d55efc9ffca98742f7575c0fa7cd9420"},
{"index-5201-30.fits.zst", "014c94da04a6e94897af09001e08bad8"},
{"index-5201-31.fits.zst", "376319584d0b6a66bcaced5b31f705d4"},
{"index-5201-32.fits.zst", "00f2873b2468d103661e6938fed2d905"},
{"index-5201-33.fits.zst", "fa1ce3020ec8511885472c0eda777cd7"},
{"index-5201-34.fits.zst", "7c66e555866806d61f90769bc626ef32"},
{"index-5201-35.fits.zst", "f1767cf0b802a97b939711f3ecd788c8"},
{"index-5201-36.fits.zst", "76825b18fef6546bbbeef3f8538a06cb"},
{"index-5201-37.fits.zst", "af507a214fc69c7daa0688fce2924c7e"},
{"index-5201-38.fits.zst", "05fc75e562c612c51bf7bacb3907aa02"},
{"index-5201-39.fits.zst", "3eeaabf9b945d71fafff7c282f9a3add"},
{"index-5201-40.fits.zst", "f891a7def591965ad4aa4ddc9cfb7718"},
{"index-5201-41.fits.zst", "48ef1d61841567de4d94d3dc366df643"},
{"index-5201-42.fits.zst", "d2c8041bbada7df9dcc5614c35edd7f1"},
{"index-5201-43.fits.zst", "24fc923bdc21f696b1da418a131dc2bc"},
{"index-5201-44.fits.zst", "690eb483b2d60e1e31ff0e71e1c19167"},
{"index-5201-45.fits.zst", "7b7972184b9bd5d485680cb10ad7f566"},
{"index-5201-46.fits.zst", "e09515bdd779241b6871eb9130980924"},
{"index-5201-47.fits.zst", "95583b10a270336b4cfb31153305b666"},
{"index-5202-00.fits.zst", "c877e6a6790d62a77753bc0b5c1c471f"},
{"index-5202-01.fits.zst", "2069168ce477a4b9c0659eb97d9d3f3e"},
{"index-5202-02.fits.zst", "80b53bdc44addc02c5a9a47183ae405e"},
{"index-5202-03.fits.zst", "fcef358afae1ac87e1072bf94c33919f"},
{"index-5202-04.fits.zst", "fb6e067de3d8f59868fc5daad9e45ac1"},
{"index-5202-05.fits.zst", "168861bd176f0c9283ef091b855cefe8"},
{"index-5202-06.fits.zst", "c88d93502450e872004d952f5cc970c6"},
{"index-5202-07.fits.zst", "0eb1b5b3b15212f734f150087872a84c"},
{"index-5202-08.fits.zst", "03a110b7092787f0da40117d3daf4ee8"},
{"index-5202-09.fits.zst", "10b89b70f19e0042c1a832dfbb0f157c"},
{"index-5202-10.fits.zst", "6d55a5356f820b437137586037049392"},
{"index-5202-11.fits.zst", "ee561de1f6ad229b1aec1d2d576cf2d6"},
{"index-5202-12.fits.zst", "16bb2e40a0a71a91b4304c0e030d9f14"},
{"index-5202-13.fits.zst", "d6259841cb5209f1fe2262a94ebba80e"},
{"index-5202-14.fits.zst", "7fcabd9e89f560dae0ea9032817ffc95"},
{"index-5202-15.fits.zst", "42c4006c6482e6a46ed81191d03a6e54"},
{"index-5202-16.fits.zst", "a726672e54dd30367664781f533a5f48"},
{"index-5202-17.fits.zst", "67fc64ba28344d9fd31143fc5123acb3"},
{"index-5202-18.fits.zst", "97ca32bc2a0ab5313547bd01485902e1"},
{"index-5202-19.fits.zst", "d261fb13fac3aa19e930d48c6cf13929"},
{"index-5202-20.fits.zst", "7a67bc4e1d1dd003280f48815d244b52"},
{"index-5202-21.fits.zst", "bbc66dabd84be8fbb47452807aa6cbd5"},
{"index-5202-22.fits.zst", "264b65ac94678334ea5dfbc4b329f2ca"},
{"index-5202-23.fits.zst", "657492ac072d1679d77abc8f532aa2c9"},
{"index-5202-24.fits.zst", "7cbd56e15c84d8b0ad605983aa0eabcb"},
{"index-5202-25.fits.zst", "5cd3457ec29821bfca8da6da1ef76684"},
{"index-5202-26.fits.zst", "253639c9680bafbbfa465d5de51de235"},
{"index-5202-27.fits.zst", "a891918b3c22f7b1e2876358a6e971e2"},
{"index-5202-28.fits.zst", "69ee777be98231c104a2e28d2c349111"},
{"index-5202-29.fits.zst", "5b9985f33d66e4da27d4c618565f35f4"},
{"index-5202-30.fits.zst", "04d6b9acb868242cf3615ca9bef4c1d8"},
{"index-5202-31.fits.zst", "24e98426ed5a60b12a6b5652b8f68ce6"},
{"index-5202-32.fits.zst", "502ca42a47d5234aab0829a387242dfc"},
{"index-5202-33.fits.zst", "253c838df836f569afe854cf598f0c79"},
{"index-5202-34.fits.zst", "8dd8e8289e9925058c9cc11e7e76c3e3"},
{"index-5202-35.fits.zst", "5b1bb19b81633bb3c2c8d1dff4bb8507"},
{"index-5202-36.fits.zst", "7990d2b9a7f120df9095d5a93d3c94f2"},
{"index-5202-37.fits.zst", "9e5f0ff891ff1b726df0001547ffd322"},
{"index-5202-38.fits.zst", "f06244e6825e4ddb101482295b5294cb"},
{"index-5202-39.fits.zst", "390f90dae3a4124cc4c7aa157e8c8597"},
{"index-5202-40.fits.zst", "b2d380ef7974fc55f0bf31ebb62ee019"},
{"index-5202-41.fits.zst", "ae8058e144898d1b786202345b6581cf"},
{"index-5202-42.fits.zst", "1247b8a91c3a9d6b10a324247c9e02c6"},
{"index-5202-43.fits.zst", "e01049718b0c6f4eb8884c647c2cdf17"},
{"index-5202-44.fits.zst", "802f0e2d56c0e4ec3d8c6d69832102d7"},
{"index-5202-45.fits.zst", "83fe2cff3cf65317f5c1bf7b953519e9"},
{"index-5202-46.fits.zst", "f12f308a3b53d95ffd7bc420700e4f44"},
{"index-5202-47.fits.zst", "608a14303810c9762b25fc68896d2a26"},
{"index-5203-00.fits.zst", "2862efb33765b7bbefb635dcad970298"},
{"index-5203-01.fits.zst", "2cd34cef4b44ad1e770396baccb2a46c"},
{"index-5203-02.fits.zst", "40c9f67282210cc374281cde023c4e71"},
{"index-5203-03.fits.zst", "c8f40e164ec3ce1df92e3a121a127716"},
{"index-5203-04.fits.zst", "cb40c64cad1d99b55dcb0b645ae388aa"},
{"index-5203-05.fits.zst", "fe1900531baaa1bb3c513b356befd522"},
{"index-5203-06.fits.zst", "8d4b7d902bacbd478b3a372338887097"},
{"index-5203-07.fits.zst", "0f18e0822ea6b67a8e5536680df16218"},
{"index-5203-08.fits.zst", "4cd0aa9bf00f903f3c71b37e047dfd2d"},
{"index-5203-09.fits.zst", "c34aeb0674c2cbd3de31e2d9b20708f0"},
{"index-5203-10.fits.zst", "64c3f710c11b5e18743d93a1e9e204f2"},
{"index-5203-11.fits.zst", "518ee18fae552e2fd83f664028219f28"},
{"index-5203-12.fits.zst", "712d6cddc97f8c183c4d9a130ba87ca4"},
{"index-5203-13.fits.zst", "185a056e25091c23bbfa425026b9897b"},
{"index-5203-14.fits.zst", "e85ade3d5b7d1c98b5d9174fb520c154"},
{"index-5203-15.fits.zst", "182d21f53ddbec1f3585936e6463b9e8"},
{"index-5203-16.fits.zst", "c2cf948d5714d61ecb6a5e235885c5ea"},
{"index-5203-17.fits.zst", "748862d448c996eda58ede16ea37b5a3"},
{"index-5203-18.fits.zst", "d145bd1cba6ccc3948fca16fd04e7efe"},
{"index-5203-19.fits.zst", "6a031fee285d47357c3cd98148c416c7"},
{"index-5203-20.fits.zst", "d08f64480576cbcb3ce1f5625e82bc87"},
{"index-5203-21.fits.zst", "5dcec75f91802cb05c36b185ea5b26de"},
{"index-5203-22.fits.zst", "c76d7ad199114e77f4e05a290eff1de8"},
{"index-5203-23.fits.zst", "3d8909cb4322a7b7baa3cd2e464269c8"},
{"index-5203-24.fits.zst", "dd5a0ce7d08940fba606546140ccd38d"},
{"index-5203-25.fits.zst", "bd27d2e07a96d7eceb26bbfff4eaf4f4"},
{"index-5203-26.fits.zst", "8d82ba9557c9b4fea8ee1a16d1cc4bb9"},
{"index-5203-27.fits.zst", "9f7e923674521562dd54903d8102bd0c"},
{"index-5203-28.fits.zst", "3064ae36821a24d67b8c53000a6b67bc"},
{"index-5203-29.fits.zst", "4eb82a64d7c9d8f7314cfda94e160e43"},
{"index-5203-30.fits.zst", "e8cf8a17c62cf0ef09b61065d1bba527"},
{"index-5203-31.fits.zst", "488fec71fc896c780aa970228d65f749"},
{"index-5203-32.fits.zst", "8d09558d167283cf5bff4feca9202421"},
{"index-5203-33.fits.zst", "c1f61ffaaee068d0a1d1829b71f46a30"},
{"index-5203-34.fits.zst", "e2567ca06041ee6995f2cb9e282fe12b"},
{"index-5203-35.fits.zst", "1c61653eb8851385a70adb15bbb8c836"},
{"index-5203-36.fits.zst", "4d5360eea4e466121f3ffc0ad2574152"},
{"index-5203-37.fits.zst", "95b713845864aa8418af634f16a0cb84"},
{"index-5203-38.fits.zst", "7ccf07966a95072e621672dfc588d127"},
{"index-5203-39.fits.zst", "0bf97501842e571c84a90d30c4b62c45"},
{"index-5203-40.fits.zst", "f0ec8a7f888c225c749dd0ca6bb946be"},
{"index-5203-41.fits.zst", "bd6fca77c9c0aae43de799b7ad823ab5"},
{"index-5203-42.fits.zst", "71ffbc8755c943c67f8b67deda4a9d44"},
{"index-5203-43.fits.zst", "4a48b878fd510a9bde3101acd7210cdb"},
{"index-5203-44.fits.zst", "1ed3d2e05dd619d145d0aac46dd69320"},
{"index-5203-45.fits.zst", "70e4d9fb4b5d66fc24990310cfc913d4"},
{"index-5203-46.fits.zst", "ecd1d7b1cb94ba52031314d189bd2390"},
{"index-5203-47.fits.zst", "894ae74eb43a8f34ad06edea62bd4337"},
{"index-5204-00.fits.zst", "6bdb9974308249e68f1ed707d6951848"},
{"index-5204-01.fits.zst", "c10ce6a6d2375bcf3e3babced3722ecd"},
{"index-5204-02.fits.zst", "9e7ed423196691e4c9f38449957860bd"},
{"index-5204-03.fits.zst", "60ccf82d3d7443423c84c789ad5d5604"},
{"index-5204-04.fits.zst", "e8ba7567c5bda04c4fb58bb93454b8ed"},
{"index-5204-05.fits.zst", "1f36a1432c055fc96582642ea5c853b2"},
{"index-5204-06.fits.zst", "bb8a67b877eeccdfef5668796a677f4f"},
{"index-5204-07.fits.zst", "2c547a8abd2410530a7547db80c40eaa"},
{"index-5204-08.fits.zst", "5be1251fcc27f3f95c38a87ba6e0335d"},
{"index-5204-09.fits.zst", "291cbc557df140dc3caccad105f9d515"},
{"index-5204-10.fits.zst", "b155a2c52e3b5a3d99d0fa5b112cd1e4"},
{"index-5204-11.fits.zst", "0a21c7bff80b6225f00e9c2213282003"},
{"index-5204-12.fits.zst", "2ca005ea103d668ebdd2f07d215dc824"},
{"index-5204-13.fits.zst", "6a3677a3e55af336dbbaa7db29492c1d"},
{"index-5204-14.fits.zst", "00c4922987950b875ddb6d68cf22dbf2"},
{"index-5204-15.fits.zst", "4faec4fdaae6ab10d91e42f77a8786b2"},
{"index-5204-16.fits.zst", "211cb590033d680cabfa3559243bbe0f"},
{"index-5204-17.fits.zst", "f8c27d1b6448ca442b5ec13d09d161ca"},
{"index-5204-18.fits.zst", "6a264515e128f61b89a5ec94d649aa05"},
{"index-5204-19.fits.zst", "d24b3fd902dbea191953d173bf85627a"},
{"index-5204-20.fits.zst", "a240cf519935d77ebda8a1ba89629b19"},
{"index-5204-21.fits.zst", "8633a5f455a70b089916bb952649abc5"},
{"index-5204-22.fits.zst", "a64cc9fc8dc5d38d530d161dde40adb0"},
{"index-5204-23.fits.zst", "639bf9f5433a272b9208094435dfacf0"},
{"index-5204-24.fits.zst", "20eece3a49f82fe2ae575bce9bc57dc9"},
{"index-5204-25.fits.zst", "6895bc172752aa20a9975e9123d6867b"},
{"index-5204-26.fits.zst", "9cb92cd20d8060dcf8c694b670800a19"},
{"index-5204-27.fits.zst", "09894bc3185f68b49cf2eb0cc7eacebe"},
{"index-5204-28.fits.zst", "bb5a2a09b531d2ca13f341cb0b00041b"},
{"index-5204-29.fits.zst", "f2d5f146ff97b86dfb4b59c8636c69d8"},
{"index-5204-30.fits.zst", "cb8bec9885e23cce0d86a94a886858ff"},
{"index-5204-31.fits.zst", "ff92d11ee8aebd9e4cd7c63006e2ba0f"},
{"index-5204-32.fits.zst", "5bc007791035420ab06a8a8dee13f50b"},
{"index-5204-33.fits.zst", "98305f6ec87af98d0a7fb82f6cb38397"},
{"index-5204-34.fits.zst", "5af466f48514b9bec75e877e3aa348e7"},
{"index-5204-35.fits.zst", "f84d32ef9278e2fa0aa013334ebddedc"},
{"index-5204-36.fits.zst", "5e7afe529e949d83812c15ca66e5fbe4"},
{"index-5204-37.fits.zst", "091d775d07623d86adf0c6f0d61da00f"},
{"index-5204-38.fits.zst", "61c3b59cd6614357da8427887fa1d7be"},
{"index-5204-39.fits.zst", "ad5687f7e7e6d65c25f52696a5be73fb"},
{"index-5204-40.fits.zst", "d95ca1f3d0abe527518ad3c4797e3b69"},
{"index-5204-41.fits.zst", "4d49cd25ea1cf1c348916b39f026a6e1"},
{"index-5204-42.fits.zst", "7f517937c94d9db3d7515cadb5cd3b10"},
{"index-5204-43.fits.zst", "f3d336795a32af76d61742c9a29bfb14"},
{"index-5204-44.fits.zst", "3372d5b85a802f891acdadfc65e05893"},
{"index-5204-45.fits.zst", "7e6c52552bf25c63af732ec7243d8766"},
{"index-5204-46.fits.zst", "5ce56079d24213af35d9ec730e12121f"},
{"index-5204-47.fits.zst", "d33d355ad900766c8fcdd53522124d01"},
{"index-5205-00.fits.zst", "d95511d75f6915caed5a4cf010e51056"},
{"index-5205-01.fits.zst", "53857e19e4ff54360ed9335c35d20ac8"},
{"index-5205-02.fits.zst", "f8bdcd851d44da92a4a90bc71deb0782"},
{"index-5205-03.fits.zst", "4782fc867bd02c58140daecc7a4f9cab"},
{"index-5205-04.fits.zst", "b63b9bfdda4a85e9377b512038aa9627"},
{"index-5205-05.fits.zst", "ffb688a56d6dc70842765a7e1fdc9ca7"},
{"index-5205-06.fits.zst", "8d906365279b2f41baa7fedd76683619"},
{"index-5205-07.fits.zst", "82ec7cc676c9ef825f218fceb236d216"},
{"index-5205-08.fits.zst", "29171a06fd40f5c5df6e637550bc7626"},
{"index-5205-09.fits.zst", "dca0e789c482eef07bee53100e10f73a"},
{"index-5205-10.fits.zst", "7d66c8c27198481c587c1432275feced"},
{"index-5205-11.fits.zst", "ce66e30646b02e7128a004cda4240b6d"},
{"index-5205-12.fits.zst", "6a42dcd534efb467a0a53c69a6047866"},
{"index-5205-13.fits.zst", "950331af7d668da1006c1b6902fd6439"},
{"index-5205-14.fits.zst", "ac6c30027cd93e91b5baf6e344032254"},
{"index-5205-15.fits.zst", "3c8d77076a49d3dc051089df8025308b"},
{"index-5205-16.fits.zst", "5c7a0c57f7bf6fcc886c7518adc2b882"},
{"index-5205-17.fits.zst", "6daa68b68104426b3e92a433107e565e"},
{"index-5205-18.fits.zst", "74b94ab3f7ee6a260560b5d78614df30"},
{"index-5205-19.fits.zst", "01597167da7a9e6fde3ace7d6e9c6788"},
{"index-5205-20.fits.zst", "3dffc55b7ab5c15e1c689c0d73f880f6"},
{"index-5205-21.fits.zst", "bfa484d631819e2a2b7a8d3dec337a9b"},
{"index-5205-22.fits.zst", "de2a3ebbf56bb640411e0a50ed0653eb"},
{"index-5205-23.fits.zst", "5498579da779e625617140b04a88659c"},
{"index-5205-24.fits.zst", "41589963565a4d1d056ac2551c94bc5b"},
{"index-5205-25.fits.zst", "88dac5e97a8e3cccd4962ee9d1f062fb"},
{"index-5205-26.fits.zst", "528044ec968e08a1347f97d2d58bc9f8"},
{"index-5205-27.fits.zst", "50890dbe9394c9101138f781394a62da"},
{"index-5205-28.fits.zst", "da541fb011826588a7ff682d3fc1065f"},
{"index-5205-29.fits.zst", "96873ae405bd9ac727656d4fbf3c508c"},
{"index-5205-30.fits.zst", "e320dad418e7e64bbd4700e074af97b6"},
{"index-5205-31.fits.zst", "5cb52c69ad1a9b780dddd82da4295f01"},
{"index-5205-32.fits.zst", "af2f00cbfc50a82138f01ee26b9e9d91"},
{"index-5205-33.fits.zst", "0e3abcccf8295f99b846e69e0a82ee55"},
{"index-5205-34.fits.zst", "02220a844210cbab3dbf32f15f25d6ff"},
{"index-5205-35.fits.zst", "21380e2a86b908f5cef98cd5b2ba5fc5"},
{"index-5205-36.fits.zst", "5898e4e3b3f4961420124fe23c106e7e"},
{"index-5205-37.fits.zst", "12a7eebcfcb9871366f27bab7bd7c02b"},
{"index-5205-38.fits.zst", "ad7ae57547afae6d7e7b5bfde0f2dd4e"},
{"index-5205-39.fits.zst", "ce92be215ddb055395db6ff1469a13c5"},
{"index-5205-40.fits.zst", "21f0f02bf765bea7577e9c379cc32aaf"},
{"index-5205-41.fits.zst", "d7bb45a9cc162262cf860554cb577cb3"},
{"index-5205-42.fits.zst", "923d46b2900879a7deb9c07a71a5a604"},
{"index-5205-43.fits.zst", "12036538e03f7e87e7e5197a176bfbeb"},
{"index-5205-44.fits.zst", "763625ff1a99a09010b4d29ee26c45f5"},
{"index-5205-45.fits.zst", "515b596b4ccb4684d84ac5bae00c3ec7"},
{"index-5205-46.fits.zst", "69c36255820c21846ce066ef9727ad9c"},
{"index-5205-47.fits.zst", "7208bf7057c156f68f8797055279c396"},
{"index-5206-00.fits.zst", "ec763f6717dc23aa74f0c37d37bbc79d"},
{"index-5206-01.fits.zst", "77c60eb07dca413177f265fd3a7358d1"},
{"index-5206-02.fits.zst", "7b04e7e1bdd5d10a7ecd8784458dfe3a"},
{"index-5206-03.fits.zst", "ff096041f96c1a928583277d53b70754"},
{"index-5206-04.fits.zst", "edfab290c5d79b16142e8e29b930276e"},
{"index-5206-05.fits.zst", "01842535f9cd6cabebdbc99eba0c2469"},
{"index-5206-06.fits.zst", "8c191abe714e0e2c709bf1b3f1e46534"},
{"index-5206-07.fits.zst", "221bc2471617105004d213b44238fa41"},
{"index-5206-08.fits.zst", "c04330df6a106b55618cc0d0467c349d"},
{"index-5206-09.fits.zst", "ac6d28cad4716da936f5f9878ecab761"},
{"index-5206-10.fits.zst", "bd79f130d1931d167a2a9cf801b05cfd"},
{"index-5206-11.fits.zst", "2e50c634e80b32ca13d643e0535a37c1"},
{"index-5206-12.fits.zst", "c132774b1cb656056d04e8175948559f"},
{"index-5206-13.fits.zst", "bc491d7a0a773f9e499b9f18f4cc2d26"},
{"index-5206-14.fits.zst", "9209a393a7341d08982925936d587178"},
{"index-5206-15.fits.zst", "416f3f4c655fdc56504030442d52f21b"},
{"index-5206-16.fits.zst", "9e6f16e687376c17c15d3f2bb7621b8f"},
{"index-5206-17.fits.zst", "4f131eff7aa8eee019dd081a250f15bd"},
{"index-5206-18.fits.zst", "7535d000a0d9ef54c1e50202319f2a4d"},
{"index-5206-19.fits.zst", "2a20fb3cf2f2bd39c9d8f0efa376e5ea"},
{"index-5206-20.fits.zst", "0e08f721f97341a0f737b4d9ffc1bafc"},
{"index-5206-21.fits.zst", "aa2b2719031262219b9e105853655d84"},
{"index-5206-22.fits.zst", "c4966b370e8e0abe7c0827712419b63f"},
{"index-5206-23.fits.zst", "baff2c6b458965754a33c2b8e4ddae30"},
{"index-5206-24.fits.zst", "f9c37d9dadf7b5c10e417909b89dd0f6"},
{"index-5206-25.fits.zst", "c420b02c3f701459762ffd24d3ee0b7f"},
{"index-5206-26.fits.zst", "f8c296fe490a6449233787f7b2275f7d"},
{"index-5206-27.fits.zst", "b63de1ef274c7b3482ec49b038c95e4a"},
{"index-5206-28.fits.zst", "0e91227a868e4d626d05f8556fd385db"},
{"index-5206-29.fits.zst", "010af2760055eb0b0f139f26808d3d0a"},
{"index-5206-30.fits.zst", "bb75c13afb642f8d1039627885591adf"},
{"index-5206-31.fits.zst", "9cbec1344ba47dd477d6d8a1f680527e"},
{"index-5206-32.fits.zst", "74e832eb93be5e6d58793418253c1b1c"},
{"index-5206-33.fits.zst", "303d2fecce3f69914d2aec9b137cd65b"},
{"index-5206-34.fits.zst", "b2fa4d6404f11552dd0ae3212a893813"},
{"index-5206-35.fits.zst", "18533d28093a1b01ba0a17811237c9c2"},
{"index-5206-36.fits.zst", "7f8a6ce0c1e0fe7998a047172bee9390"},
{"index-5206-37.fits.zst", "eef8a7d8de31e0865de6a2563cd54602"},
{"index-5206-38.fits.zst", "27d0395d69aa600d2c334664ee88191d"},
{"index-5206-39.fits.zst", "455e2ee060c16a560e62df0bf6790027"},
{"index-5206-40.fits.zst", "9d7f12a0adfb97d7d3b904bf6f8788c4"},
{"index-5206-41.fits.zst", "d05556c1d3c15f0cdb72363a82ab6d7c"},
{"index-5206-42.fits.zst", "99268fca7e2a9161f4f1c144b13dea3a"},
{"index-5206-43.fits.zst", "e3b6becbf0949d9c40dac1d366805493"},
{"index-5206-44.fits.zst", "eb6802ea492c8ab920699a47cd8e5ccf"},
{"index-5206-45.fits.zst", "d3f692ee8ee9d6c9d3483818f2b81584"},
{"index-5206-46.fits.zst", "aff3a7ba7140e5e850c1395fba6402c0"},
{"index-5206-47.fits.zst", "27b479b738a7cd3379e105638b1fc43e"}
};
Download::Download(QNetworkReply *reply, const QString indexPath, QObject *parent) : QObject(parent)
,_reply(reply)
,_hash(QCryptographicHash::Md5)
{
connect(_reply, &QNetworkReply::finished, this, &Download::finished);
connect(_reply, &QNetworkReply::readyRead, this, &Download::readData);
connect(_reply, &QNetworkReply::downloadProgress, this, &Download::progress);
QString filename = _reply->url().fileName();
filename.remove(QRegularExpression("\\.zst$"));
_fw.setFileName(indexPath + "/" + filename);
_fw.open(QIODevice::WriteOnly | QIODevice::Truncate);
if(_fw.isOpen())
{
qDebug() << "open file" << _fw.fileName();
}
_dstream = ZSTD_createDStream();
}
Download::~Download()
{
ZSTD_freeDStream(_dstream);
}
void Download::abort()
{
_reply->abort();
}
void Download::readData()
{
QByteArray data = _reply->readAll();
decompress(data);
}
void Download::finished()
{
if(_reply->error() == QNetworkReply::NoError)
{
QByteArray data = _reply->readAll();
qDebug() << "finished" << data.size();
decompress(data);
if(md5.contains(_reply->url().fileName()))
{
if(_hash.result().toHex() == md5[_reply->url().fileName()])
qDebug() << "DOWNLOAD OK";
else
{
qDebug() << "DOWNLOAD BAD";
_fw.remove();
return;
}
}
_fw.flush();
_fw.close();
}
else
{
qDebug() << "Failed to perform http request" << _reply->url();
_fw.remove();
}
}
void Download::decompress(QByteArray &data)
{
if(data.isEmpty())return;
_hash.addData(data);
ZSTD_inBuffer inBuffer = {data.constData(), static_cast<size_t>(data.size()), 0};
QByteArray outData(ZSTD_DStreamOutSize(), '\0');
while(inBuffer.pos < inBuffer.size)
{
ZSTD_outBuffer outBuffer = {outData.data(), static_cast<size_t>(outData.size()), 0};
size_t ret = ZSTD_decompressStream(_dstream, &outBuffer, &inBuffer);
if(ZSTD_isError(ret))
{
qDebug() << "decompress error" << ZSTD_getErrorName(ret);
_fw.remove();
_reply->abort();
break;
}
else if(outBuffer.pos)
{
_fw.write(static_cast<char*>(outBuffer.dst), outBuffer.pos);
}
}
}
HttpDownloader::HttpDownloader(QObject *parent) : QObject(parent)
,_manager(new QNetworkAccessManager(this))
{
_manager->setAutoDeleteReplies(true);
connect(_manager, &QNetworkAccessManager::finished, this, &HttpDownloader::finished);
#ifdef PLATESOLVER
QDir dir(Solver::getTenmonIndexPath());
if(!dir.exists())
{
if(dir.mkpath("."))
qDebug() << "Failed to create astrometry directory";
}
_indexPath = dir.absolutePath();
#endif
}
void HttpDownloader::download(const QUrl &url)
{
if(!_queue.contains(url))
_queue.enqueue(url);
if(!_download)
finished();
}
bool HttpDownloader::downloadIndex(int scale)
{
if(scale > 19 || scale < 1)
return false;
QUrl url("https://tenmon.nouspiro.space/");
QStringList files = indexFileNames(scale);
for(auto &file : files)
{
if(QFile::exists(_indexPath + "/" + file))
{
qDebug() << "File already exists, skipping" << file;
}
else
{
url.setPath("/astrometry/" + file + ".zst");
download(url);
}
}
return true;
}
void HttpDownloader::abort()
{
if(_download)
_download->abort();
}
QStringList HttpDownloader::indexFileNames(int scale)
{
QStringList ret;
if(scale >= 7)
{
ret.append(QString("index-%1.fits").arg(4100 + scale));
}
else
{
for(int i=0; i<48; i++)
ret.append(QString("index-%1-%2.fits").arg(5200 + scale).arg(i, 2, 10, QChar('0')));
}
return ret;
}
void HttpDownloader::finished()
{
if(_queue.isEmpty())
{
_download = nullptr;
}
else
{
QUrl url = _queue.dequeue();
QString filename = url.fileName();
filename.remove(QRegularExpression("\\.zst$"));
QFileInfo info(_indexPath + "/" + filename);
if(info.exists())
{
finished();
return;
}
QNetworkRequest request(url);
_download = new Download(_manager->get(request), _indexPath, this);
connect(_download, &Download::progress, this, &HttpDownloader::updateProgress);
}
}
void HttpDownloader::updateProgress(qint64 received, qint64 total)
{
emit progress((float)received / total * 100.0f, _queue.size());
}
+52
View File
@@ -0,0 +1,52 @@
#ifndef HTTPDOWNLOADER_H
#define HTTPDOWNLOADER_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QFile>
#include <QQueue>
#include <QCryptographicHash>
#include <zstd.h>
class Download : public QObject
{
Q_OBJECT
QNetworkReply *_reply;
ZSTD_DStream *_dstream;
QFile _fw;
QCryptographicHash _hash;
public:
Download(QNetworkReply *reply, const QString indexPath, QObject *parent);
~Download();
void abort();
public slots:
void readData();
void finished();
signals:
void progress(qint64 received, qint64 total);
protected:
void decompress(QByteArray &data);
};
class HttpDownloader : public QObject
{
Q_OBJECT
QNetworkAccessManager *_manager;
Download *_download = nullptr;
QQueue<QUrl> _queue;
QString _indexPath;
public:
explicit HttpDownloader(QObject *parent = nullptr);
void download(const QUrl &url);
// scale in range 19-1
bool downloadIndex(int scale);
void abort();
static QStringList indexFileNames(int scale);
signals:
void progress(int percent, int files);
protected slots:
void finished();
void updateProgress(qint64 received, qint64 total);
};
#endif // HTTPDOWNLOADER_H
+173 -15
View File
@@ -51,14 +51,6 @@ FITSRecord::FITSRecord(const LibXISF::Property &property)
xisf = true;
}
QByteArray FITSRecord::valueToByteArray() const
{
if(value.type() == QVariant::Bool)
return value.toBool() ? "T" : "F";
else
return value.toString().toLatin1();
}
ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
{
setColumnCount(3);
@@ -98,14 +90,14 @@ void ImageInfo::setInfo(const ImageInfoData &info)
expandAll();
}
void WCSData::freeWCS()
void WCSDataT::freeWCS()
{
wcsvfree(&nwcs, &wcs);
nwcs = 0;
wcs = nullptr;
}
WCSData::WCSData(int width, int height, char *header, int nrec) :
WCSDataT::WCSDataT(int width, int height, char *header, int nrec) :
width(width),
height(height)
{
@@ -121,7 +113,7 @@ WCSData::WCSData(int width, int height, char *header, int nrec) :
freeWCS();
}
WCSData::WCSData(int width, int height, const QVector<FITSRecord> &header) :
WCSDataT::WCSDataT(int width, int height, const QVector<FITSRecord> &header) :
width(width),
height(height)
{
@@ -156,13 +148,13 @@ WCSData::WCSData(int width, int height, const QVector<FITSRecord> &header) :
freeWCS();
}
WCSData::~WCSData()
WCSDataT::~WCSDataT()
{
if(wcs)
freeWCS();
}
bool WCSData::pixelToWorld(const QPointF &pixel, SkyPoint &point) const
bool WCSDataT::pixelToWorld(const QPointF &pixel, SkyPoint &point) const
{
if(!valid())return false;
@@ -181,7 +173,7 @@ bool WCSData::pixelToWorld(const QPointF &pixel, SkyPoint &point) const
return false;
}
bool WCSData::worldToPixel(const SkyPoint &point, QPointF &pixel) const
bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const
{
if(!valid())return false;
@@ -200,7 +192,7 @@ bool WCSData::worldToPixel(const SkyPoint &point, QPointF &pixel) const
return false;
}
void WCSData::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
{
if(wcs == nullptr)return;
@@ -257,6 +249,34 @@ void WCSData::calculateBounds(double &minRa, double &maxRa, double &minDec, doub
}
}
double hav(double x)
{
return (1.0 - std::cos(x)) * 0.5;
}
double haverSine(const SkyPoint &a, SkyPoint &b)
{
const double ToRAD = M_PI / 180.0;
double d = hav((a.DEC() - b.DEC()) * ToRAD) + std::cos(a.DEC() * ToRAD) * std::cos(b.DEC() * ToRAD) * hav((a.RA() - b.RA()) * ToRAD);
return std::acos(1.0 - 2.0 * d) * (180.0 / M_PI);
}
SkyPointScale WCSDataT::getRaDecScale() const
{
SkyPointScale ret;
pixelToWorld(QPointF(width/2.0, height/2.0), ret.point);
SkyPoint pointX;
SkyPoint pointY;
pixelToWorld(QPointF(width/2.0+1, height/2.0), pointX);
pixelToWorld(QPointF(width/2.0, height/2.0+1), pointY);
double scaleX = haverSine(ret.point, pointX) * 3600.0;
double scaleY = haverSine(ret.point, pointY) * 3600.0;
ret.scaleLow = std::min(scaleX, scaleY);
ret.scaleHigh = std::max(scaleX, scaleY);
ret.scaleValid = true;
return ret;
}
SkyPoint::SkyPoint() : ra(NAN), dec(NAN)
{
}
@@ -284,3 +304,141 @@ QString SkyPoint::toString() const
sec = std::modf(min, &min) * 60;
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');
}
double SkyPoint::fromHMS(const QString &hms)
{
double deg = fromDMS(hms);
if(std::isnan(deg))return deg;
return deg * 15.0;
}
double SkyPoint::fromDMS(const QString &dms)
{
double deg = 0.0;
QString str = dms.trimmed();
str.remove(QRegularExpression("[hdms°'\"]"));
str.replace(':', ' ');
str.replace(QRegularExpression("\\s+"), " ");
QStringList fields = str.split(' ');
double sign = 1.0;
bool ok = false;
if(fields.size() >= 1)
deg = fields.at(0).toDouble(&ok);
if(!ok)return NAN;
if(deg < 0.0)
sign = -1.0;
if(fields.size() >= 2)
deg += sign * fields.at(1).toDouble() / 60.0;
if(fields.size() >= 3)
deg += sign * fields.at(2).toDouble() / 3600.0;
return deg;
}
QString SkyPoint::toHMS(double decHour)
{
double h,m,s,md;
md = std::modf(decHour, &h) * 60.0;
s = std::modf(md, &m) * 60.0;
return QString("%1h %2m %3s").arg((int)h, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
}
QString SkyPoint::toDMS(double deg)
{
double d,m,s,md;
md = std::modf(deg, &d) * 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'));
}
SkyPointScale ImageInfoData::getCenterRaDec() const
{
SkyPointScale ret;
if(wcs && wcs->valid())
{
ret = wcs->getRaDecScale();
}
else
{
double ra,dec,focalLen,scale,pixSizeX,pixSizeY;
int binX = 1;
int binY = 1;
ra = dec = focalLen = scale = pixSizeX = pixSizeY = NAN;
bool ok;
for(const FITSRecord &header : fitsHeader)
{
if(header.key == "OBJCTRA")
{
double tmp = SkyPoint::fromHMS(header.value.toString());
if(!std::isnan(tmp))ra = tmp;
}
else if(header.key == "RA" && std::isnan(ra))
{
double tmp = header.value.toDouble(&ok);
if(ok)ra = tmp;
}
else if(header.key == "OBJCTDEC")
{
double tmp = SkyPoint::fromDMS(header.value.toString());
if(!std::isnan(tmp))dec = tmp;
}
else if(header.key == "DEC" && std::isnan(dec))
{
double tmp = SkyPoint::fromDMS(header.value.toString());
if(!std::isnan(tmp))dec = tmp;
}
else if(header.key == "SCALE")
{
double tmp = header.value.toDouble(&ok);
if(ok)scale = tmp;
}
else if(header.key == "FOCALLEN")
{
double tmp = header.value.toDouble(&ok);
if(ok)focalLen = tmp;
}
else if(header.key == "PIXSIZE1" || header.key == "XPIXSZ")
{
pixSizeX = header.value.toDouble();
}
else if(header.key == "PIXSIZE2" || header.key == "YPIXSZ")
{
pixSizeY = header.value.toDouble();
}
else if(header.key == "XBINNING")
{
int tmp = header.value.toInt(&ok);
if(ok)binX = tmp;
}
else if(header.key == "YBINNING")
{
int tmp = header.value.toInt(&ok);
if(ok)binY = tmp;
}
}
ret.point.set(ra, dec);
if(!std::isnan(scale))
{
ret.scaleLow = ret.scaleHigh = scale;
ret.scaleValid = true;
}
else if(!(std::isnan(focalLen) || std::isnan(pixSizeX) || std::isnan(pixSizeY)))
{
const double r = 206.2648097656; // (180 * 3600) / (1000 * pi) magic number to convert pixel size to focal length ratio to arcsec.
ret.scaleLow = std::min(pixSizeX * binX / focalLen * r, pixSizeY * binY / focalLen * r);
ret.scaleHigh = std::max(pixSizeX * binX / focalLen * r, pixSizeY * binY / focalLen * r);
ret.scaleValid = true;
}
}
if(ret.scaleValid)
{
ret.scaleLow *= 0.8;
ret.scaleHigh *= 1.2;
}
return ret;
}
+22 -7
View File
@@ -19,7 +19,6 @@ struct FITSRecord
FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment);
FITSRecord(const LibXISF::FITSKeyword &record);
FITSRecord(const LibXISF::Property &property);
QByteArray valueToByteArray() const;
};
class SkyPoint
@@ -31,11 +30,25 @@ public:
SkyPoint(double ra, double dec);
void set(double ra, double dec);
double RA() const { return ra; }
double RAHour() const { return ra / 15.0; }
double DEC() const { return dec; }
QString toString() const;
static double fromHMS(const QString &hms);
static double fromDMS(const QString &dms);
static QString toHMS(double decHour);
static QString toDMS(double deg);
};
class WCSData
struct SkyPointScale
{
SkyPoint point;
//arcsec per pixel
bool scaleValid = false;
double scaleLow = 0.0;
double scaleHigh = 10000.0;
};
class WCSDataT
{
int nwcs = 0;
struct wcsprm *wcs = nullptr;
@@ -43,21 +56,23 @@ class WCSData
int height;
void freeWCS();
public:
WCSData(int width, int height, char *header, int nrec);
WCSData(int width, int height, const QVector<FITSRecord> &header);
WCSData(const WCSData &) = delete;
~WCSData();
WCSDataT(int width, int height, char *header, int nrec);
WCSDataT(int width, int height, const QVector<FITSRecord> &header);
WCSDataT(const WCSDataT &) = delete;
~WCSDataT();
bool pixelToWorld(const QPointF &pixel, SkyPoint &point) 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 valid() const { return wcs; };
SkyPointScale getRaDecScale() const;
};
struct ImageInfoData
{
QVector<FITSRecord> fitsHeader;
QVector<QPair<QString, QString>> info;
std::shared_ptr<WCSData> wcs;
std::shared_ptr<WCSDataT> wcs;
SkyPointScale getCenterRaDec() const;
};
Q_DECLARE_METATYPE(ImageInfoData);
+51 -21
View File
@@ -22,13 +22,13 @@ Image::Image(const QString name, int number, ImageRingList *ringList) :
{
}
void Image::load()
void Image::load(QThreadPool *pool)
{
if(!m_rawImage && !m_loading)
{
m_loading = true;
m_released = false;
QThreadPool::globalInstance()->start(new LoadRunable(m_name, this, m_ringList->analyzeLevel()));
pool->start(new LoadRunable(m_name, this, m_ringList->analyzeLevel()));
}
if(!m_loading && m_rawImage)
emit pixmapLoaded(this);
@@ -110,18 +110,26 @@ ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter,
{
connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
m_nameFilter.replaceInStrings(QRegularExpression("^"), "*.");
m_loadPool = new QThreadPool(this);
m_loadPool->setThreadPriority(QThread::LowPriority);
m_thumbPool = new QThreadPool(this);
m_thumbPool->setThreadPriority(QThread::LowPriority);
m_slideShowTimer = new QTimer(this);
connect(m_slideShowTimer, &QTimer::timeout, this, static_cast<void (ImageRingList::*)()>(&ImageRingList::increment));
m_dirChangeDelay = new QTimer(this);
m_dirChangeDelay->setInterval(3000);
m_dirChangeDelay->setSingleShot(true);
connect(m_dirChangeDelay, &QTimer::timeout, this, &ImageRingList::reloadDir);
}
ImageRingList::~ImageRingList()
{
QThreadPool::globalInstance()->clear();
m_loadPool->clear();
m_thumbPool->clear();
QThreadPool::globalInstance()->waitForDone();
m_loadPool->waitForDone();
m_thumbPool->waitForDone();
}
@@ -168,11 +176,14 @@ bool ImageRingList::setDir(const QString path, const QString &currentFile, bool
void ImageRingList::setFile(const QString &file)
{
QFileInfo info(file);
if(info.isDir())
setDir(file, QString(), true);
else
setDir(info.absolutePath(), file);
if(!file.isEmpty())
{
QFileInfo info(file);
if(info.isDir())
setDir(file, QString(), true);
else
setDir(info.absolutePath(), file);
}
}
ImagePtr ImageRingList::currentImage()
@@ -194,9 +205,9 @@ void ImageRingList::increment()
(*m_firstImage)->release();
m_firstImage = increment(m_firstImage);
m_currImage = increment(m_currImage);
(*m_currImage)->load();
(*m_currImage)->load(m_loadPool);
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load();
(*m_lastImage)->load(m_loadPool);
}
}
@@ -207,12 +218,22 @@ void ImageRingList::decrement()
(*m_lastImage)->release();
m_firstImage = decrement(m_firstImage);
m_currImage = decrement(m_currImage);
(*m_currImage)->load();
(*m_currImage)->load(m_loadPool);
m_lastImage = decrement(m_lastImage);
(*m_firstImage)->load();
(*m_firstImage)->load(m_loadPool);
}
}
void ImageRingList::setMarked()
{
QStringList files = m_database->getMarkedFiles();
std::remove_if(files.begin(), files.end(), [](const QString &file){
QFileInfo info(file);
return !info.exists() || !info.isReadable();
});
setFiles(files);
}
void ImageRingList::setLiveMode(bool live)
{
m_liveMode = live;
@@ -253,7 +274,7 @@ void ImageRingList::loadFile(int row)
if(m_images.empty())
return;
(*m_currImage)->load();
(*m_currImage)->load(m_loadPool);
m_width = DEFAULT_WIDTH<m_images.size()/2 ? DEFAULT_WIDTH : m_images.size()/2;
if(m_liveMode)
@@ -262,9 +283,9 @@ void ImageRingList::loadFile(int row)
for(int i=0; i<m_width; i++)
{
m_firstImage = decrement(m_firstImage);
(*m_firstImage)->load();
(*m_firstImage)->load(m_loadPool);
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load();
(*m_lastImage)->load(m_loadPool);
}
if(m_lastImage != m_firstImage)
{
@@ -388,9 +409,9 @@ void ImageRingList::setPreload(int width)
for(int i = newWidth - m_width; i>0; i--)
{
m_firstImage = decrement(m_firstImage);
(*m_firstImage)->load();
(*m_firstImage)->load(m_loadPool);
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load();
(*m_lastImage)->load(m_loadPool);
}
}
if(newWidth < m_width)
@@ -445,9 +466,9 @@ void ImageRingList::toggleSlideshow(bool start)
void ImageRingList::setFiles(const QStringList files, const QString &currentFile)
{
QThreadPool::globalInstance()->clear();
m_loadPool->clear();
m_thumbPool->clear();
QThreadPool::globalInstance()->waitForDone();
m_loadPool->waitForDone();
m_thumbPool->waitForDone();
beginResetModel();
m_images.clear();
@@ -496,13 +517,22 @@ void ImageRingList::imageLoaded(Image *image)
}
void ImageRingList::dirChanged(QString dir)
{
m_currentDir = dir;
if(m_liveMode)
reloadDir();
else
m_dirChangeDelay->start();
}
void ImageRingList::reloadDir()
{
QString currentFile;
if(m_images.size())
currentFile = (*m_currImage)->name();
setDir(dir, currentFile);
setDir(m_currentDir, currentFile);
if(m_images.size())
emit currentImageChanged(m_currImage-m_images.begin());
}
+6 -1
View File
@@ -27,7 +27,7 @@ class Image : public QObject
ImageRingList *m_ringList;
public:
explicit Image(const QString name, int number, ImageRingList *ringList);
void load();
void load(QThreadPool *pool);
void loadThumbnail(QThreadPool *pool);
void release();
QString name() const;
@@ -62,10 +62,13 @@ class ImageRingList : public QAbstractItemModel
QDir::SortFlag m_sort = QDir::Name;
bool m_reversed = false;
AnalyzeLevel m_analyzeLevel;
QThreadPool *m_loadPool;
QThreadPool *m_thumbPool;
Database *m_database;
QStringList m_nameFilter;
QTimer *m_slideShowTimer;
QTimer *m_dirChangeDelay;
QString m_currentDir;
public:
explicit ImageRingList(Database *database, const QStringList &nameFilter, QObject *parent = 0);
~ImageRingList() override;
@@ -98,6 +101,7 @@ public slots:
void toggleSlideshow(bool start);
void increment();
void decrement();
void setMarked();
protected:
void setFiles(const QStringList files, const QString &currentFile = QString());
QList<ImagePtr>::iterator increment(QList<ImagePtr>::iterator iter);
@@ -110,6 +114,7 @@ signals:
protected slots:
void imageLoaded(Image *image);
void dirChanged(QString dir);
void reloadDir();
};
#endif // IMAGERINGLIST_H
+114 -87
View File
@@ -1,121 +1,148 @@
#include "imagescrollarea.h"
#include <QMouseEvent>
#include <QScrollBar>
#include <QKeyEvent>
#include <QPalette>
#include "imageringlist.h"
#include <QDebug>
#include <QKeyEvent>
#include <QGridLayout>
#include <QMimeData>
#include <QMessageBox>
#include <QCoreApplication>
#include <QPainter>
#include <QFileInfo>
#include <QScrollBar>
#include <cmath>
ImageScrollArea::ImageScrollArea(QWidget *parent) : QScrollArea(parent),
m_scale(-1)
ImageScrollArea::ImageScrollArea(Database *database, QWidget *parent) : QWidget(parent)
{
m_label = new QLabel(this);
setWidget(m_label);
setAlignment(Qt::AlignCenter);
setBackgroundRole(QPalette::Dark);
QGridLayout *layout = new QGridLayout(this);
setLayout(layout);
ImageWidgetGL *imageWidgetGL = new ImageWidgetGL(database, this);
m_imageWidget = imageWidgetGL;
m_verticalScrollBar = new QScrollBar(Qt::Vertical, this);
m_horizontalScrollBar = new QScrollBar(Qt::Horizontal, this);
layout->setSpacing(0);
layout->addWidget(dynamic_cast<ImageWidgetGL*>(m_imageWidget), 0, 0);
layout->addWidget(m_verticalScrollBar, 0, 1);
layout->addWidget(m_horizontalScrollBar, 1, 0);
connect(m_verticalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollEvent()));
connect(m_horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollEvent()));
if(imageWidgetGL)
{
connect(imageWidgetGL, &ImageWidgetGL::fileDropped, this, &ImageScrollArea::fileDropped);
connect(imageWidgetGL, &ImageWidgetGL::status, this, &ImageScrollArea::status);
connect(imageWidgetGL, &ImageWidgetGL::scrollBarsUpdate, this, &ImageScrollArea::updateScrollbars);
}
}
void ImageScrollArea::setImage(const QPixmap &img)
ImageScrollArea::~ImageScrollArea()
{
m_pixmap = img;
QPixmap pix;
if(m_scale < 0)
pix = img.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
void ImageScrollArea::allocateThumbnails(const QStringList &paths)
{
m_imageWidget->allocateThumbnails(paths);
}
void ImageScrollArea::showThumbnail(bool enable)
{
m_imageWidget->showThumbnail(enable);
}
void ImageScrollArea::setBayerMask(int mask)
{
m_imageWidget->setBayerMask(mask);
}
void ImageScrollArea::updateScrollbars(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV)
{
if(maxH > 0)
{
m_horizontalScrollBar->show();
m_horizontalScrollBar->setRange(0, maxH);
m_horizontalScrollBar->setPageStep(stepH);
m_horizontalScrollBar->setValue(valueH);
}
else
pix = img.scaled(img.size() * m_scale, Qt::KeepAspectRatio, Qt::SmoothTransformation);
m_horizontalScrollBar->hide();
m_label->setPixmap(pix);
m_label->resize(pix.size());
horizontalScrollBar()->setValue(horizontalScrollBar()->maximum() / 2);
verticalScrollBar()->setValue(verticalScrollBar()->maximum() / 2);
}
void ImageScrollArea::setScale(float scale)
{
if(scale > 4 || (scale < 0.2 && scale > 0) || m_pixmap.isNull())
return;
m_scale = scale;
QSize newSize = m_scale < 0 ? size() : m_pixmap.size()*scale;
m_label->setPixmap(m_pixmap.scaled(newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_label->resize(newSize);
if(maxV > 0)
{
m_verticalScrollBar->show();
m_verticalScrollBar->setRange(0, maxV);
m_verticalScrollBar->setPageStep(stepV);
m_verticalScrollBar->setValue(valueV);
}
else
m_verticalScrollBar->hide();
}
void ImageScrollArea::zoomIn()
{
if(m_scale < 0)
m_scale = (float)size().width()/m_pixmap.size().width();
setScale(m_scale + 0.1);
m_imageWidget->zoom(1);
}
void ImageScrollArea::zoomOut()
{
if(m_scale < 0)
m_scale = (float)size().width()/m_pixmap.size().width();
setScale(m_scale - 0.1);
m_imageWidget->zoom(-1);
}
void ImageScrollArea::bestFit()
{
setScale(-1);
m_horizontalScrollBar->hide();
m_verticalScrollBar->hide();
m_imageWidget->bestFit();
}
void ImageScrollArea::oneToOne()
{
setScale(1);
m_imageWidget->zoom(0);
}
void ImageScrollArea::keyPressEvent(QKeyEvent *event)
void ImageScrollArea::imageLoaded(Image *image)
{
event->ignore();
}
void ImageScrollArea::keyReleaseEvent(QKeyEvent *event)
{
event->ignore();
}
void ImageScrollArea::mouseMoveEvent(QMouseEvent *event)
{
QPoint delta = m_lastPos - event->pos();
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + delta.x());
verticalScrollBar()->setValue(verticalScrollBar()->value() + delta.y());
m_lastPos = event->pos();
}
void ImageScrollArea::mousePressEvent(QMouseEvent *event)
{
m_lastPos = event->pos();
}
void ImageScrollArea::resizeEvent(QResizeEvent *event)
{
if(m_scale < 0 && !m_pixmap.isNull())
if(image && image->rawImage())
{
m_label->setPixmap(m_pixmap.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_label->resize(event->size());
m_imageWidget->setImage(image->rawImage(), image->number());
m_imageWidget->setWCS(image->info().wcs);
}
QScrollArea::resizeEvent(event);
}
void ImageScrollArea::wheelEvent(QWheelEvent *event)
void ImageScrollArea::thumbnailLoaded(const Image *image)
{
if(m_scale < 0)
m_scale = (float)size().width()/m_pixmap.size().width();
QPointF top(horizontalScrollBar()->value(), verticalScrollBar()->value());
QPointF mousePos = (top + event->position()) / m_scale;
QPoint delta = event->angleDelta();
if(delta.y() > 0)
setScale(m_scale + 0.1);
else
setScale(m_scale - 0.1);
mousePos *= m_scale;
top = mousePos - event->position();
horizontalScrollBar()->setValue(top.x());
verticalScrollBar()->setValue(top.y());
m_imageWidget->thumbnailLoaded(image);
}
void ImageScrollArea::setMTFParams(const MTFParam &params)
{
m_imageWidget->setMTFParams(params);
}
void ImageScrollArea::invert(bool enable)
{
m_imageWidget->invert(enable);
}
void ImageScrollArea::superPixel(bool enable)
{
m_imageWidget->superPixel(enable);
}
void ImageScrollArea::falseColor(bool enable)
{
m_imageWidget->falseColor(enable);
}
QImage ImageScrollArea::renderToImage()
{
return m_imageWidget->renderToImage();
}
void ImageScrollArea::scrollEvent()
{
m_imageWidget->setOffset(m_horizontalScrollBar->value(), m_verticalScrollBar->value());
}
+26 -17
View File
@@ -1,32 +1,41 @@
#ifndef IMAGESCROLLAREA_H
#define IMAGESCROLLAREA_H
#include <QScrollArea>
#include <QLabel>
#include "imagewidget.h"
class ImageScrollArea : public QScrollArea
class ImageScrollArea : public QWidget
{
Q_OBJECT
QPoint m_lastPos;
QLabel *m_label;
QPixmap m_pixmap;
float m_scale;
QScrollBar *m_verticalScrollBar;
QScrollBar *m_horizontalScrollBar;
ImageWidget *m_imageWidget;
public:
explicit ImageScrollArea(QWidget *parent = 0);
void setImage(const QPixmap &img);
void setScale(float scale);
explicit ImageScrollArea(Database *database, QWidget *parent = nullptr);
~ImageScrollArea();
void allocateThumbnails(const QStringList &paths);
void showThumbnail(bool enable);
void setBayerMask(int mask);
protected:
void updateScrollbars(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV);
public slots:
void zoomIn();
void zoomOut();
void bestFit();
void oneToOne();
protected:
void keyPressEvent(QKeyEvent *event) override;
void keyReleaseEvent(QKeyEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void imageLoaded(Image *image);
void thumbnailLoaded(const Image *image);
void setMTFParams(const MTFParam &params);
void invert(bool enable);
void superPixel(bool enable);
void falseColor(bool enable);
QImage renderToImage();
protected slots:
void scrollEvent();
signals:
void fileDropped(const QString &path);
void status(const QString &value, const QString &pixelCoords, const QString &celestialCoords);
void scrollBarsUpdate(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV);
};
#endif // IMAGESCROLLAREA_H
+338 -216
View File
@@ -1,21 +1,20 @@
#include "imagescrollareagl.h"
#include <QOpenGLFunctions>
#include <QOpenGLVersionFunctionsFactory>
#include <QDebug>
#include <QKeyEvent>
#include <QOpenGLDebugLogger>
#include <QOpenGLPixelTransferOptions>
#include <QOpenGLFramebufferObject>
#include <QGridLayout>
#include <QMimeData>
#include <QMessageBox>
#include "imagewidget.h"
#include <QCoreApplication>
#include <QPainter>
#include <QFileInfo>
#include <cmath>
#include <QMessageBox>
#include <QTimer>
#include <QElapsedTimer>
#include <QOpenGLFramebufferObject>
#include <QOpenGLExtraFunctions>
#include <QOpenGLDebugLogger>
#include <QMimeData>
#include <QDragEnterEvent>
#include <QPainter>
#include "imageringlist.h"
#include <QFloat16>
int FILTERING = 1;
bool OpenGLES = false;
const int LUT_SIZE = 32;
struct RawImageType
{
@@ -31,7 +30,7 @@ RawImageType getRawImageType(const RawImage *img)
{
case RawImage::UINT8:
if(img->channels() >= 3)
type.textureFormat = QOpenGLTexture::SRGB8_Alpha8;
type.textureFormat = QOpenGLTexture::RGBA8_UNorm;
else
type.textureFormat = QOpenGLTexture::R8_UNorm;
type.dataType = QOpenGLTexture::UInt8;
@@ -50,6 +49,13 @@ RawImageType getRawImageType(const RawImage *img)
type.textureFormat = QOpenGLTexture::R32F;
type.dataType = QOpenGLTexture::Float32;
break;
case RawImage::FLOAT16:
if(img->channels() >= 3)
type.textureFormat = QOpenGLTexture::RGBA16F;
else
type.textureFormat = QOpenGLTexture::R16F;
type.dataType = QOpenGLTexture::Float16;
break;
default:
qWarning() << "Invalid format" << img->type();
break;
@@ -63,8 +69,8 @@ RawImageType getRawImageType(const RawImage *img)
return type;
}
ImageWidget::ImageWidget(Database *database, QWidget *parent) : QOpenGLWidget(parent)
, m_database(database)
ImageWidgetGL::ImageWidgetGL(Database *database, QWidget *parent) : QOpenGLWidget(parent)
, m_database(database)
{
setFocusPolicy(Qt::ClickFocus);
m_updateTimer = new QTimer(this);
@@ -76,19 +82,19 @@ ImageWidget::ImageWidget(Database *database, QWidget *parent) : QOpenGLWidget(pa
if(!isValid())
{
QMessageBox::critical(this, tr("OpenGL error"), tr("Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed."));
QCoreApplication::exit(-1);
QCoreApplication::exit(-1);
}
});
setMouseTracking(true);
}
ImageWidget::~ImageWidget()
ImageWidgetGL::~ImageWidgetGL()
{
makeCurrent();
}
void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
{
m_currentImg = index;
@@ -105,33 +111,48 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
m_error.clear();
makeCurrent();
m_rawImage = image;
if((int)image->width() > m_maxTextureSize || (int)image->height() > m_maxTextureSize)
m_rawImage->resize(std::min(m_maxTextureSize, (int)image->width()), std::min(m_maxTextureSize, (int)image->height()));
m_imgWidth = image->width();
m_imgHeight = image->height();
bool tooBig = false;
if((int)image->width() > m_maxTextureSize || (int)image->height() > m_maxTextureSize)
{
tooBig = true;
m_swPaint = true;
}
if(!m_image)return;
RawImageType rawImageType = getRawImageType(image.get());
m_srgb = rawImageType.textureFormat == QOpenGLTexture::SRGB8_Alpha8;
m_srgb = image->getLUT().size() > 0;
m_bwImg = image->channels() == 1;
QElapsedTimer timer;
timer.start();
m_image->destroy();
m_image->setAutoMipMapGenerationEnabled(false);
m_image->setFormat(rawImageType.textureFormat);
m_image->setSize(image->width(), image->height());
m_image->setMipLevels([&](){ int c = 0; int s = std::min(m_imgWidth, m_imgHeight); while(s>>=1)c++; return c; }());
m_image->allocateStorage();
m_image->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);
m_image->setWrapMode(QOpenGLTexture::ClampToEdge);
m_image->setBorderColor(0, 0, 0, 0);
m_image->setData(0, rawImageType.pixelFormat, rawImageType.dataType, (const void*)image->data(), m_transferOptions.get());
m_image->generateMipMaps();
qDebug() << "setImage" << timer.elapsed();
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if(m_srgb)
{
m_lut->setData(0, 0, 0, LUT_SIZE, LUT_SIZE, LUT_SIZE, 0, QOpenGLTexture::RGBA, QOpenGLTexture::RGBA, QOpenGLTexture::Float16, image->getLUT().data());
}
if(!tooBig)
{
while(f->glGetError() != GL_NO_ERROR);
QElapsedTimer timer;
timer.start();
m_image->destroy();
m_image->setAutoMipMapGenerationEnabled(false);
m_image->setFormat(rawImageType.textureFormat);
m_image->setSize(image->width(), image->height());
m_image->setMipLevels([&](){ int c = 0; int s = std::min(m_imgWidth, m_imgHeight); while(s>>=1)c++; return c; }());
m_image->allocateStorage();
m_image->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);
m_image->setWrapMode(QOpenGLTexture::ClampToEdge);
m_image->setData(0, rawImageType.pixelFormat, rawImageType.dataType, (const void*)image->data());
m_image->bind();
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
f->glGenerateMipmap(GL_TEXTURE_2D);
qDebug() << "setImage" << timer.elapsed();
m_swPaint = f->glGetError() != GL_NO_ERROR;
}
m_unit_scale[0] = 1.0f;
m_unit_scale[1] = 0.0f;
@@ -152,12 +173,12 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
else setOffset(m_dx, m_dy);
}
void ImageWidget::setWCS(std::shared_ptr<WCSData> wcs)
void ImageWidgetGL::setWCS(std::shared_ptr<WCSDataT> wcs)
{
m_wcs = wcs;
}
void ImageWidget::zoom(int zoom, const QPointF &mousePos)
void ImageWidgetGL::zoom(int zoom, const QPointF &mousePos)
{
m_bestFit = false;
if(zoom != 0)
@@ -169,10 +190,10 @@ void ImageWidget::zoom(int zoom, const QPointF &mousePos)
if(!mousePos.isNull())
focus = mousePos;
if(width() > m_image->width() * m_scale)
m_dx = -width() * 0.5f + m_image->width() * m_scale * 0.5f;
if(height() > m_image->height() * m_scale)
m_dy = -height() * 0.5f + m_image->height() * m_scale * 0.5f;
if(width() > m_imgWidth * m_scale)
m_dx = -width() * 0.5f + m_imgWidth * m_scale * 0.5f;
if(height() > m_imgHeight * m_scale)
m_dy = -height() * 0.5f + m_imgHeight * m_scale * 0.5f;
float newScale = std::sqrt(std::pow(2.0f, (float)m_scaleStop));
float r = newScale / m_scale;
@@ -181,14 +202,14 @@ void ImageWidget::zoom(int zoom, const QPointF &mousePos)
setOffset(m_dx * r + focus.x() * (r - 1), m_dy * r + focus.y() * (r - 1));
}
void ImageWidget::bestFit()
void ImageWidgetGL::bestFit()
{
m_bestFit = true;
m_scale = std::min((float)m_width/m_imgWidth, (float)m_height/m_imgHeight);
setOffset(0, 0);
}
void ImageWidget::allocateThumbnails(const QStringList &paths)
void ImageWidgetGL::allocateThumbnails(const QStringList &paths)
{
makeCurrent();
int count = paths.size();
@@ -202,27 +223,30 @@ void ImageWidget::allocateThumbnails(const QStringList &paths)
}
m_thumbnailTexture->destroy();
m_thumbnailTexture->create();
m_thumbnailTexture->setFormat(QOpenGLTexture::RGB16_UNorm);
m_thumbnailTexture->setFormat(QOpenGLTexture::RGBA16F);
m_thumbnailTexture->setSize(THUMB_SIZE, THUMB_SIZE);
m_thumbnailTexture->setLayers(std::min((int)paths.size(), m_maxArrayLayers));
m_thumbnailTexture->setAutoMipMapGenerationEnabled(false);
m_thumbnailTexture->setWrapMode(QOpenGLTexture::ClampToEdge);
m_thumbnailTexture->setMipLevelRange(0, 0);
m_thumbnailTexture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
m_thumbnailTexture->allocateStorage();
}
QVector2D ImageWidget::getImagePixelCoord(const QVector2D &pos)
QVector2D ImageWidgetGL::getImagePixelCoord(const QVector2D &pos)
{
float dx = m_dx;
float dy = m_dy;
if(m_width > m_image->width()*m_scale)
dx = -width()*0.5f + m_image->width()*m_scale*0.5f;
if(m_height > m_image->height()*m_scale)
dy = -height()*0.5f + m_image->height()*m_scale*0.5f;
if(m_width > m_imgWidth * m_scale)
dx = -width()*0.5f + m_imgWidth*m_scale * 0.5f;
if(m_height > m_imgHeight * m_scale)
dy = -height()*0.5f + m_imgHeight*m_scale * 0.5f;
QVector2D offset(dx, dy);
return (pos + offset) / m_scale;
}
void ImageWidget::setBayerMask(int mask)
void ImageWidgetGL::setBayerMask(int mask)
{
m_firstRed[0] = mask & 0x1;
m_firstRed[1] = (mask & 0x2) >> 1;
@@ -234,13 +258,13 @@ void ImageWidget::setBayerMask(int mask)
update();
}
void ImageWidget::setMTFParams(const MTFParam &params)
void ImageWidgetGL::setMTFParams(const MTFParam &params)
{
m_mtfParams = params;
update();
}
void ImageWidget::setOffset(float dx, float dy)
void ImageWidgetGL::setOffset(float dx, float dy)
{
m_dx = std::clamp(dx, 0.0f, std::max(0.0f, m_imgWidth * m_scale - m_width));
if(m_showThumbnails)
@@ -251,25 +275,25 @@ void ImageWidget::setOffset(float dx, float dy)
update();
}
void ImageWidget::superPixel(bool enable)
void ImageWidgetGL::superPixel(bool enable)
{
m_superpixel = enable;
update();
}
void ImageWidget::invert(bool enable)
void ImageWidgetGL::invert(bool enable)
{
m_invert = enable;
update();
}
void ImageWidget::falseColor(bool enable)
void ImageWidgetGL::falseColor(bool enable)
{
m_falseColor = enable;
update();
}
QImage ImageWidget::renderToImage()
QImage ImageWidgetGL::renderToImage()
{
if(m_imgWidth < 0)return QImage();
makeCurrent();
@@ -277,6 +301,7 @@ QImage ImageWidget::renderToImage()
fbo.bind();
f->glViewport(0, 0, m_imgWidth, m_imgHeight);
m_vao->bind();
m_program->bind();
m_program->setUniformValue("viewport", (float)m_imgWidth, (float)m_imgHeight);
@@ -284,16 +309,20 @@ QImage ImageWidget::renderToImage()
m_program->setUniformValue("zoom", 1.0f);
if(m_superpixel && m_debayerTex)
{
f->glActiveTexture(GL_TEXTURE0);
f->glBindTexture(GL_TEXTURE_2D, m_debayerTex);
}
else
m_image->bind(0);
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_vao->release();
fbo.bindDefault();
return fbo.toImage(true);
}
void ImageWidget::thumbnailLoaded(const Image *image)
void ImageWidgetGL::thumbnailLoaded(const Image *image)
{
if(image->number() >= m_maxArrayLayers)
return;
@@ -301,7 +330,9 @@ void ImageWidget::thumbnailLoaded(const Image *image)
makeCurrent();
const RawImage *raw = image->thumbnail();
if(!raw || !raw->valid())return;
m_thumbnailTexture->setData(0, image->number(), QOpenGLTexture::RGBA, QOpenGLTexture::UInt16, raw->data(), m_transferOptions.get());
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
m_thumbnailTexture->setData(0, image->number(), QOpenGLTexture::RGBA, QOpenGLTexture::Float16, raw->data());
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
float a = raw->thumbAspect();
int sizes[3] = { std::max(1, a > 1.0f ? THUMB_SIZE : (int)(THUMB_SIZE * a)), std::max(1, a < 1.0f ? THUMB_SIZE : (int)(THUMB_SIZE / a)), image->number() };
m_sizesDirty = true;
@@ -309,23 +340,164 @@ void ImageWidget::thumbnailLoaded(const Image *image)
if(!m_updateTimer->isActive())m_updateTimer->start();
}
void ImageWidget::showThumbnail(bool enable)
void ImageWidgetGL::showThumbnail(bool enable)
{
m_showThumbnails = enable;
setOffset(m_dx, m_dy);
}
void ImageWidget::paintGL()
void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scale, const MTFParam &mtfParams, QWidget *widget)
{
QPainter painter(widget);
int width = widget->width();
int height = widget->height();
QImage img(width, height, QImage::Format_RGB32);
img.fill(Qt::darkGray);
int64_t ox = dx;
int64_t oy = dy;
auto mtf = [&mtfParams](int i, float x)
{
x = (x - mtfParams.blackPoint[i]) / (mtfParams.whitePoint[i] - mtfParams.blackPoint[i]);
x = std::min(std::max(x, 0.0f), 1.0f);
return ((mtfParams.midPoint[i] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[i] - 1.0f) * x - mtfParams.midPoint[i]);
};
int imgWidth = rawImage->width();
int imgHeight = rawImage->height();
auto convert = [&](auto *src)
{
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();
float iscale = 1.0f / scale;
float r[4];
float g[4];
float b[4];
for(int64_t y = std::max((int64_t)0, -oy); y < height; y++)
{
uint32_t *pixels = (uint32_t*)(img.scanLine(y));
float iptr;
float fy = std::modf((y + oy) * iscale, &iptr);
int64_t py = iptr;
int64_t w = py * rawImage->widthBytes();
int64_t w2 = w;
if(py+1 < imgHeight)w2 += rawImage->widthBytes();
if(py >= imgHeight)break;
for(int64_t x = std::max((int64_t)0, -ox); x < width; x++)
{
float fx = std::modf((x + ox) * iscale, &iptr);
int px = iptr;
int px2 = px + 1 < imgWidth ? px + 1 : px;
if(px >= imgWidth)break;
if(rawImage->channels() > 1)
{
r[0] = src[w + px * 4 + 0];
g[0] = src[w + px * 4 + 1];
b[0] = src[w + px * 4 + 2];
if(FILTERING)
{
r[1] = src[w + px2 * 4 + 0];
g[1] = src[w + px2 * 4 + 1];
b[1] = src[w + px2 * 4 + 2];
r[2] = src[w2 + px * 4 + 0];
g[2] = src[w2 + px * 4 + 1];
b[2] = src[w2 + px * 4 + 2];
r[3] = src[w2 + px2 * 4 + 0];
g[3] = src[w2 + px2 * 4 + 1];
b[3] = src[w2 + px2 * 4 + 2];
}
}
else
{
r[0] = src[w + px];
if(FILTERING)
{
r[2] = src[w2 + px];
r[1] = src[w + px2];
r[3] = src[w2 + px2];
}
}
uint32_t rgb = 0xff000000;
if(FILTERING)
{
if(rawImage->channels() > 1)
{
rgb |= (uint8_t)(mtf(0, ((r[3] * fx + r[2] * (1.0f - fx)) * fy + (r[1] * fx + r[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f) << 16;
rgb |= (uint8_t)(mtf(1, ((g[3] * fx + g[2] * (1.0f - fx)) * fy + (g[1] * fx + g[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f) << 8;
rgb |= (uint8_t)(mtf(1, ((b[3] * fx + b[2] * (1.0f - fx)) * fy + (b[1] * fx + b[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f);
}
else
{
uint32_t v = (uint8_t)(mtf(0, ((r[3] * fx + r[2] * (1.0f - fx)) * fy + (r[1] * fx + r[0] * (1.0f - fx)) * (1.0f - fy)) / s) * 255.0f);
rgb = 0xff000000 | (v << 16) | (v << 8) | v;
}
}
else
{
if(rawImage->channels() > 1)
{
rgb |= (uint8_t)(mtf(0, r[0] / s) * 255.0f) << 16;
rgb |= (uint8_t)(mtf(1, g[0] / s) * 255.0f) << 8;
rgb |= (uint8_t)(mtf(1, b[0] / s) * 255.0f);
}
else
{
uint32_t v = (uint8_t)(mtf(0, r[0] / s) * 255.0f);
rgb = 0xff000000 | (v << 16) | (v << 8) | v;
}
}
pixels[x] = rgb;
}
}
};
if(rawImage)
{
switch(rawImage->type())
{
case RawImage::UINT8:
convert(static_cast<uint8_t*>(rawImage->data()));
break;
case RawImage::UINT16:
convert(static_cast<uint16_t*>(rawImage->data()));
break;
case RawImage::UINT32:
convert(static_cast<uint32_t*>(rawImage->data()));
break;
case RawImage::FLOAT16:
convert(static_cast<qfloat16*>(rawImage->data()));
break;
case RawImage::FLOAT32:
convert(static_cast<float*>(rawImage->data()));
break;
case RawImage::FLOAT64:
convert(static_cast<double*>(rawImage->data()));
break;
}
}
painter.drawImage(0, 0, img);
}
void ImageWidgetGL::paintGL()
{
float dx = m_dx;
float dy = m_dy;
if(m_width > m_image->width() * m_scale)
dx = -width() * 0.5f + m_image->width() * m_scale * 0.5f;
if(m_height > m_image->height() * m_scale)
dy = -height() * 0.5f + m_image->height() * m_scale * 0.5f;
if(m_width > m_imgWidth * m_scale)
dx = -width() * 0.5f + m_imgWidth * m_scale * 0.5f;
if(m_height > m_imgHeight * m_scale)
dy = -height() * 0.5f + m_imgHeight * m_scale * 0.5f;
QBrush highlight = style()->standardPalette().highlight();
f->glClear(GL_COLOR_BUFFER_BIT);
f->glBlendFunc(GL_ONE, GL_ZERO);
if(m_showThumbnails)
{
m_vaoThumb->bind();
@@ -351,16 +523,20 @@ void ImageWidget::paintGL()
m_thumbnailProgram->setUniformValueArray("mtf_param", m_mtfParams.blackPoint, 3, 3);
m_thumbnailProgram->setUniformValue("invert", m_invert);
m_thumbnailProgram->setUniformValue("offset", 0, m_dy);
QMatrix4x4 mvp;
mvp.ortho(rect());
m_thumbnailProgram->setUniformValue("mvp", mvp);
if(f3)f3->glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, m_thumbnailCount);
QPainter painter(this);
const int w = width()/THUMB_SIZE_BORDER;
const int off = (THUMB_SIZE_BORDER - THUMB_SIZE) / 2;
int start = std::max((int)(m_dy / THUMB_SIZE_BORDER_Y * w - w), 0);
int end = std::min((int)(m_dy + m_height) / THUMB_SIZE_BORDER_Y * w + w, m_thumbnailCount);
fx->glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, m_thumbnailCount);
m_vaoThumb->release();
QPainter painter(this);
for(int i=start; i < end; i++)
{
float x = (i % w) * THUMB_SIZE_BORDER;
@@ -395,33 +571,43 @@ void ImageWidget::paintGL()
}
else
{
debayer();
m_vao->bind();
if(m_superpixel && m_debayerTex)
f->glBindTexture(GL_TEXTURE_2D, m_debayerTex);
if(m_swPaint)
{
swPaint(m_rawImage, dx, dy, m_scale, m_mtfParams, this);
}
else
m_image->bind(0);
{
m_vao->bind();
debayer();
m_program->bind();
m_program->setUniformValue("viewport", (float)width(), (float)height());
m_program->setUniformValue("offset", std::floor(dx), std::floor(dy));
m_program->setUniformValueArray("mtf_param", m_mtfParams.blackPoint, 3, 3);
m_program->setUniformValue("unit_scale", m_unit_scale[0], m_unit_scale[1]);
m_program->setUniformValue("zoom", 1.0f/m_scale);
m_program->setUniformValue("bw", m_bwImg && !m_superpixel);
m_program->setUniformValue("false_color", m_falseColor && m_bwImg);
m_program->setUniformValue("invert", m_invert);
m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1);
#ifdef COLOR_MANAGMENT
m_program->setUniformValue("srgb", m_srgb);
#endif
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
if(m_superpixel && m_debayerTex)
{
f->glActiveTexture(GL_TEXTURE0);
f->glBindTexture(GL_TEXTURE_2D, m_debayerTex);
}
else
m_image->bind(0);
m_program->bind();
m_program->setUniformValue("viewport", (float)width(), (float)height());
m_program->setUniformValue("offset", std::floor(dx), std::floor(dy));
m_program->setUniformValueArray("mtf_param", m_mtfParams.blackPoint, 3, 3);
m_program->setUniformValue("unit_scale", m_unit_scale[0], m_unit_scale[1]);
m_program->setUniformValue("zoom", 1.0f/m_scale);
m_program->setUniformValue("bw", m_bwImg && !m_superpixel);
m_program->setUniformValue("false_color", m_falseColor && m_bwImg);
m_program->setUniformValue("invert", m_invert);
m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1);
m_program->setUniformValue("lut_table", 2);
m_program->setUniformValue("srgb", m_srgb);
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_vao->release();
}
}
}
void ImageWidget::resizeGL(int w, int h)
void ImageWidgetGL::resizeGL(int w, int h)
{
m_width = w;
m_height = h;
@@ -430,15 +616,39 @@ void ImageWidget::resizeGL(int w, int h)
updateScrollBars();
}
void ImageWidget::initializeGL()
void ImageWidgetGL::initializeGL()
{
f = context()->functions();
fx = context()->extraFunctions();
f->glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
f3 = QOpenGLVersionFunctionsFactory::get<QOpenGLFunctions_3_3_Core>(context());
if(f3 == nullptr)
if(fx == nullptr)
QMessageBox::critical(this, tr("OpenGL error"), tr("Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed."));
OpenGLES = context()->isOpenGLES();
auto loadShader = [](const QString &file)
{
QFile fr(file);
fr.open(QIODevice::ReadOnly);
QByteArray src;
if(OpenGLES)
{
src = "#version 300 es\n"
"precision highp float;\n"
"precision highp sampler2D;\n"
"precision highp sampler2DArray;\n"
"precision highp sampler3D;\n"
"#line 1\n";
}
else
{
src = "#version 330\n#line 1\n";
}
src.append(fr.readAll());
return src;
};
m_vao = std::unique_ptr<QOpenGLVertexArrayObject>(new QOpenGLVertexArrayObject);
m_vaoThumb = std::unique_ptr<QOpenGLVertexArrayObject>(new QOpenGLVertexArrayObject);
m_vao->create();
@@ -449,9 +659,9 @@ void ImageWidget::initializeGL()
logger->initialize();
logger->startLogging();
connect(logger, &QOpenGLDebugLogger::messageLogged, [](const QOpenGLDebugMessage &message)
{
qDebug() << message;
});
{
qDebug() << message;
});
qDebug() << "Vendor:" << (char*)f->glGetString(GL_VENDOR);
qDebug() << "Renderer:" << (char*)f->glGetString(GL_RENDERER);
@@ -466,9 +676,9 @@ void ImageWidget::initializeGL()
// each vertex is x,y 2D position and s,t texture coordinates
float vertexs[] = {-1.0f, -1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 1.0f, 0.0f,};
1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 1.0f, 0.0f,};
m_buffer = std::unique_ptr<QOpenGLBuffer>(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer));
m_buffer->setUsagePattern(QOpenGLBuffer::StaticDraw);
m_buffer->create();
@@ -477,8 +687,8 @@ void ImageWidget::initializeGL()
// f->glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(float)*4, 0);
m_program = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/image.vert");
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/image.frag");
m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShader(":/image.vert"));
m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShader(":/image.frag"));
if(!m_program->link())
{
@@ -491,11 +701,12 @@ void ImageWidget::initializeGL()
m_program->enableAttributeArray("qt_MultiTexCoord0");
m_program->setAttributeBuffer("qt_MultiTexCoord0", GL_FLOAT, sizeof(float)*2, 2, sizeof(float)*4);
m_program->setUniformValue("qt_Texture0", (GLuint)0);
m_program->setUniformValue("lut_table", (GLuint)2);
m_program->setUniformValue("scale", 1.0f, 0.0f);
m_debayerProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/debayer.vert");
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/debayer.frag");
m_debayerProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShader(":/debayer.vert"));
m_debayerProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShader(":/debayer.frag"));
m_debayerProgram->bind();
m_debayerProgram->enableAttributeArray("qt_Vertex");
@@ -511,8 +722,8 @@ void ImageWidget::initializeGL()
m_vaoThumb->bind();
m_thumbnailProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/thumb.vert");
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/thumb.frag");
m_thumbnailProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShader(":/thumb.vert"));
m_thumbnailProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShader(":/thumb.frag"));
m_thumbnailProgram->bind();
m_thumbnailProgram->enableAttributeArray("qt_Vertex");
@@ -532,8 +743,9 @@ void ImageWidget::initializeGL()
m_bufferSizes->allocate(12);
m_thumbnailProgram->enableAttributeArray("imageSize_num");
f3->glVertexAttribIPointer(m_thumbnailProgram->attributeLocation("imageSize_num"), 3, GL_INT, 0, nullptr);
f3->glVertexAttribDivisor(m_thumbnailProgram->attributeLocation("imageSize_num"), 1);
fx->glVertexAttribIPointer(m_thumbnailProgram->attributeLocation("imageSize_num"), 3, GL_INT, 0, nullptr);
fx->glVertexAttribDivisor(m_thumbnailProgram->attributeLocation("imageSize_num"), 1);
m_vaoThumb->release();
m_image = std::unique_ptr<QOpenGLTexture>(new QOpenGLTexture(QOpenGLTexture::Target2D));
m_image->setFormat(QOpenGLTexture::RGB8U);
@@ -543,27 +755,26 @@ void ImageWidget::initializeGL()
m_image->setMagnificationFilter(QOpenGLTexture::Linear);
m_thumbnailTexture = std::unique_ptr<QOpenGLTexture>(new QOpenGLTexture(QOpenGLTexture::Target2DArray));
m_thumbnailTexture->setFormat(QOpenGLTexture::RGB16_UNorm);
m_thumbnailTexture->setSize(THUMB_SIZE, THUMB_SIZE);
m_thumbnailTexture->setLayers(1);
m_thumbnailTexture->allocateStorage();
m_thumbnailTexture->bind(1);
m_thumbnailTexture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
m_transferOptions = std::unique_ptr<QOpenGLPixelTransferOptions>(new QOpenGLPixelTransferOptions);
m_transferOptions->setAlignment(1);
m_lut = std::make_unique<QOpenGLTexture>(QOpenGLTexture::Target3D);
m_lut->setSize(LUT_SIZE, LUT_SIZE, LUT_SIZE);
m_lut->setMipLevelRange(0, 0);
m_lut->setFormat(QOpenGLTexture::TextureFormat::RGBA16F);
m_lut->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
m_lut->allocateStorage();
m_lut->bind(2);
if(m_rawImage)
setImage(m_rawImage, m_currentImg);
}
void ImageWidget::dragEnterEvent(QDragEnterEvent *event)
void ImageWidgetGL::dragEnterEvent(QDragEnterEvent *event)
{
if(event->mimeData()->hasUrls() && event->proposedAction() & (Qt::CopyAction | Qt::MoveAction))
event->acceptProposedAction();
}
void ImageWidget::dropEvent(QDropEvent *event)
void ImageWidgetGL::dropEvent(QDropEvent *event)
{
if(event->mimeData()->hasUrls() && event->proposedAction() & (Qt::CopyAction | Qt::MoveAction))
{
@@ -580,7 +791,7 @@ void ImageWidget::dropEvent(QDropEvent *event)
event->ignore();
}
void ImageWidget::mousePressEvent(QMouseEvent *event)
void ImageWidgetGL::mousePressEvent(QMouseEvent *event)
{
if(m_showThumbnails && event->button() == Qt::LeftButton && event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier))
m_selecting = true;
@@ -596,7 +807,7 @@ void ImageWidget::mousePressEvent(QMouseEvent *event)
}
}
void ImageWidget::mouseMoveEvent(QMouseEvent *event)
void ImageWidgetGL::mouseMoveEvent(QMouseEvent *event)
{
if(m_selecting)
{
@@ -631,7 +842,7 @@ void ImageWidget::mouseMoveEvent(QMouseEvent *event)
}
}
void ImageWidget::mouseReleaseEvent(QMouseEvent *event)
void ImageWidgetGL::mouseReleaseEvent(QMouseEvent *event)
{
if(m_selecting)
{
@@ -662,7 +873,7 @@ void ImageWidget::mouseReleaseEvent(QMouseEvent *event)
}
}
void ImageWidget::wheelEvent(QWheelEvent *event)
void ImageWidgetGL::wheelEvent(QWheelEvent *event)
{
if(m_showThumbnails)
{
@@ -675,7 +886,7 @@ void ImageWidget::wheelEvent(QWheelEvent *event)
}
}
void ImageWidget::thumbSelect(QMouseEvent *event)
void ImageWidgetGL::thumbSelect(QMouseEvent *event)
{
QPoint p = event->pos();
const int off = (THUMB_SIZE_BORDER - THUMB_SIZE) / 2;
@@ -704,11 +915,11 @@ void ImageWidget::thumbSelect(QMouseEvent *event)
}
}
void ImageWidget::debayer()
void ImageWidgetGL::debayer()
{
if(m_debayerTex > 0 || !m_superpixel || !m_bwImg || m_imgWidth < 0)return;
QOpenGLFramebufferObject fbo(m_imgWidth, m_imgHeight, QOpenGLFramebufferObject::NoAttachment, GL_TEXTURE_2D, GL_RGBA16);
QOpenGLFramebufferObject fbo(m_imgWidth, m_imgHeight, QOpenGLFramebufferObject::NoAttachment, GL_TEXTURE_2D, GL_RGBA16F);
fbo.bind();
f->glViewport(0, 0, m_imgWidth, m_imgHeight);
@@ -722,104 +933,15 @@ void ImageWidget::debayer()
f->glViewport(0, 0, m_width, m_height);
m_debayerTex = fbo.takeTexture();
f->glBindTexture(GL_TEXTURE_2D, m_debayerTex);
f->glGenerateMipmap(GL_TEXTURE_2D);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
f->glGenerateMipmap(GL_TEXTURE_2D);
}
void ImageWidget::updateScrollBars()
void ImageWidgetGL::updateScrollBars()
{
if(m_showThumbnails)
emit scrollBarsUpdate(0, 0, -1, m_dy, m_height, (m_thumbnailCount / (m_width / THUMB_SIZE_BORDER) + 2) * THUMB_SIZE_BORDER_Y - m_height);
else
emit scrollBarsUpdate(m_dx, m_width, m_imgWidth * m_scale - m_width, m_dy, m_height, m_imgHeight * m_scale - m_height);
}
ImageScrollAreaGL::ImageScrollAreaGL(Database *database, QWidget *parent) : QWidget(parent)
{
QGridLayout *layout = new QGridLayout(this);
setLayout(layout);
m_imageWidget = new ImageWidget(database, this);
m_verticalScrollBar = new QScrollBar(Qt::Vertical, this);
m_horizontalScrollBar = new QScrollBar(Qt::Horizontal, this);
layout->setSpacing(0);
layout->addWidget(m_imageWidget, 0, 0);
layout->addWidget(m_verticalScrollBar, 0, 1);
layout->addWidget(m_horizontalScrollBar, 1, 0);
connect(m_verticalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollEvent()));
connect(m_horizontalScrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollEvent()));
connect(m_imageWidget, &ImageWidget::scrollBarsUpdate, this, &ImageScrollAreaGL::updateScrollbars);
}
ImageScrollAreaGL::~ImageScrollAreaGL()
{
}
void ImageScrollAreaGL::setImage(Image *image)
{
if(image && image->rawImage())
{
m_imageWidget->setImage(image->rawImage(), image->number());
m_imageWidget->setWCS(image->info().wcs);
}
}
ImageWidget *ImageScrollAreaGL::imageWidget()
{
return m_imageWidget;
}
void ImageScrollAreaGL::updateScrollbars(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV)
{
if(maxH > 0)
{
m_horizontalScrollBar->show();
m_horizontalScrollBar->setRange(0, maxH);
m_horizontalScrollBar->setPageStep(stepH);
m_horizontalScrollBar->setValue(valueH);
}
else
m_horizontalScrollBar->hide();
if(maxV > 0)
{
m_verticalScrollBar->show();
m_verticalScrollBar->setRange(0, maxV);
m_verticalScrollBar->setPageStep(stepV);
m_verticalScrollBar->setValue(valueV);
}
else
m_verticalScrollBar->hide();
}
void ImageScrollAreaGL::zoomIn()
{
m_imageWidget->zoom(1);
}
void ImageScrollAreaGL::zoomOut()
{
m_imageWidget->zoom(-1);
}
void ImageScrollAreaGL::bestFit()
{
m_horizontalScrollBar->hide();
m_verticalScrollBar->hide();
m_imageWidget->bestFit();
}
void ImageScrollAreaGL::oneToOne()
{
m_imageWidget->zoom(0);
}
void ImageScrollAreaGL::scrollEvent()
{
m_imageWidget->setOffset(m_horizontalScrollBar->value(), m_verticalScrollBar->value());
}
+52 -53
View File
@@ -1,21 +1,43 @@
#ifndef IMAGESCROLLAREAGL_H
#define IMAGESCROLLAREAGL_H
#ifndef IMAGEWIDGET_H
#define IMAGEWIDGET_H
#include <memory>
#include <QObject>
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QScrollBar>
#include <QTimer>
#include "rawimage.h"
#include "imageringlist.h"
#include <QOpenGLFunctions>
#include "database.h"
#include "rawimage.h"
#include "imageinfo.h"
#include "stretchtoolbar.h"
class ImageWidget
{
public:
ImageWidget(){}
virtual ~ImageWidget(){}
virtual void setImage(std::shared_ptr<RawImage> image, int index) = 0;
virtual void setWCS(std::shared_ptr<WCSDataT> wcs) = 0;
virtual void zoom(int zoom, const QPointF &mousePos = QPointF()) = 0;
virtual void bestFit() = 0;
virtual void setBayerMask(int mask) = 0;
virtual void setOffset(float dx, float dy) = 0;
virtual void allocateThumbnails(const QStringList &paths) = 0;
virtual void setMTFParams(const MTFParam &params) = 0;
virtual void superPixel(bool enable) = 0;
virtual void invert(bool enable) = 0;
virtual void falseColor(bool enable) = 0;
virtual QImage renderToImage() = 0;
virtual void thumbnailLoaded(const Image *image) = 0;
virtual void showThumbnail(bool enable) = 0;
};
struct ImageThumb
{
QString name;
@@ -25,11 +47,11 @@ struct ImageThumb
bool dirty;
};
class ImageWidget : public QOpenGLWidget
class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
{
Q_OBJECT
QOpenGLFunctions *f = nullptr;
QOpenGLFunctions_3_3_Core *f3 = nullptr;
QOpenGLExtraFunctions *fx = nullptr;
QTimer *m_updateTimer = nullptr;
std::unique_ptr<QOpenGLShaderProgram> m_program;
std::unique_ptr<QOpenGLShaderProgram> m_thumbnailProgram;
@@ -39,11 +61,11 @@ class ImageWidget : public QOpenGLWidget
std::unique_ptr<QOpenGLTexture> m_image;
std::unique_ptr<QOpenGLVertexArrayObject> m_vao;
std::unique_ptr<QOpenGLVertexArrayObject> m_vaoThumb;
std::unique_ptr<QOpenGLPixelTransferOptions> m_transferOptions;
std::unique_ptr<QOpenGLTexture> m_thumbnailTexture;
std::unique_ptr<QOpenGLTexture> m_lut;
GLuint m_debayerTex = 0;
std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSData> m_wcs;
std::shared_ptr<WCSDataT> m_wcs;
int m_width, m_height;
int m_imgWidth = -1, m_imgHeight = -1;
int m_currentImg = 0;
@@ -69,26 +91,25 @@ class ImageWidget : public QOpenGLWidget
Database *m_database = nullptr;
QPointF m_lastPos;
QString m_error;
bool m_swPaint = false;
public:
explicit ImageWidget(Database *database, QWidget *parent = nullptr);
~ImageWidget() override;
void setImage(std::shared_ptr<RawImage> image, int index);
void setImage(const QPixmap &pixmap);
void setWCS(std::shared_ptr<WCSData> wcs);
void zoom(int zoom, const QPointF &mousePos = QPointF());
void bestFit();
void allocateThumbnails(const QStringList &paths);
explicit ImageWidgetGL(Database *database, QWidget *parent = nullptr);
~ImageWidgetGL() override;
void setImage(std::shared_ptr<RawImage> image, int index) override;
void setWCS(std::shared_ptr<WCSDataT> wcs) override;
void zoom(int zoom, const QPointF &mousePos = QPointF()) override;
void bestFit() override;
void allocateThumbnails(const QStringList &paths) override;
QVector2D getImagePixelCoord(const QVector2D &pos);
void setBayerMask(int mask);
public slots:
void setMTFParams(const MTFParam &params);
void setOffset(float dx, float dy);
void superPixel(bool enable);
void invert(bool enable);
void falseColor(bool enable);
QImage renderToImage();
void thumbnailLoaded(const Image *image);
void showThumbnail(bool enable);
void setBayerMask(int mask) override;
void setOffset(float dx, float dy) override;
void setMTFParams(const MTFParam &params) override;
void superPixel(bool enable) override;
void invert(bool enable) override;
void falseColor(bool enable) override;
QImage renderToImage() override;
void thumbnailLoaded(const Image *image) override;
void showThumbnail(bool enable) override;
protected:
void paintGL() override;
void resizeGL(int w, int h) override;
@@ -108,26 +129,4 @@ signals:
void scrollBarsUpdate(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV);
};
class ImageScrollAreaGL : public QWidget
{
Q_OBJECT
QScrollBar *m_verticalScrollBar;
QScrollBar *m_horizontalScrollBar;
ImageWidget *m_imageWidget;
public:
explicit ImageScrollAreaGL(Database *database, QWidget *parent = nullptr);
~ImageScrollAreaGL() override;
void setImage(Image *image);
ImageWidget* imageWidget();
protected:
void updateScrollbars(int valueH, int stepH, int maxH, int valueV, int stepV, int maxV);
public slots:
void zoomIn();
void zoomOut();
void bestFit();
void oneToOne();
protected slots:
void scrollEvent();
};
#endif // IMAGESCROLLAREAGL_H
#endif // IMAGEWIDGET_H
+1 -1
Submodule libXISF updated: d00de2041d...9a32138f6a
+70 -33
View File
@@ -13,14 +13,25 @@
#include <libxisf.h>
#include "rawimage.h"
#include "starfit.h"
#include "wcslib/wcshdr.h"
#include <lcms2.h>
#ifdef COLOR_MANAGMENT
#include <QColorSpace>
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(file),
m_file(makeMaxPath(file)),
m_receiver(receiver),
m_analyzeLevel(level),
m_thumbnail(thumbnail)
@@ -180,14 +191,14 @@ int loadFITSHeader(fitsfile *file, ImageInfoData &info)
fits_hdr2str(file, TRUE, (char**)exclist, 2, &header, &nrec, &status);
if(status == 0)
{
info.wcs = std::make_shared<WCSData>(naxes[0], naxes[1], header, nrec);
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 loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
{
fitsfile *file;
int status = 0;
@@ -265,14 +276,11 @@ bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
s[i] -= INT16_MIN;
}
if(img.channels() == 1)
if(img.channels() == 1 || planar)
image = std::make_shared<RawImage>(std::move(img));
else
image = RawImage::fromPlanar(img);
if(image)
image->convertToGLFormat();
break;
}
}
@@ -301,7 +309,7 @@ bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
return true;
}
bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image)
bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
{
try
{
@@ -321,7 +329,7 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
info.fitsHeader.append(prop);
}
info.wcs = std::make_shared<WCSData>(xisfImage.width(), xisfImage.height(), info.fitsHeader);
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();
@@ -343,16 +351,25 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
{
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)
{
image = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
}
if(image)
{
image->convertToGLFormat();
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)
{
@@ -381,9 +398,15 @@ void LoadRunable::run()
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();
@@ -418,6 +441,7 @@ void LoadRunable::run()
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));
@@ -429,7 +453,12 @@ void LoadRunable::run()
}
catch(std::exception e)
{
qDebug() << m_file << e.what();
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()));
}
}
@@ -437,7 +466,8 @@ bool readFITSHeader(const QString &path, ImageInfoData &info)
{
fitsfile *fr;
int status = 0;
fits_open_diskfile(&fr, path.toLocal8Bit().data(), READONLY, &status);
QString path2 = makeMaxPath(path);
fits_open_diskfile(&fr, path2.toLocal8Bit().data(), READONLY, &status);
if(fr && status == 0)
{
@@ -449,10 +479,11 @@ bool readFITSHeader(const QString &path, ImageInfoData &info)
bool readXISFHeader(const QString &path, ImageInfoData &info)
{
QString path2 = makeMaxPath(path);
try
{
LibXISF::XISFReader xisf;
xisf.open(path.toLocal8Bit().data());
xisf.open(path2.toLocal8Bit().data());
const LibXISF::Image &image = xisf.getImage(0, false);
auto fitskeywords = image.fitsKeywords();
@@ -466,7 +497,7 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
{
info.fitsHeader.append(prop);
}
info.wcs = std::make_shared<WCSData>(image.width(), image.height(), info.fitsHeader);
info.wcs = std::make_shared<WCSDataT>(image.width(), image.height(), info.fitsHeader);
if(!info.wcs->valid())info.wcs.reset();
}
catch (LibXISF::Error &err)
@@ -477,7 +508,7 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
return true;
}
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage)
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar)
{
bool ret = false;
QElapsedTimer timer;
@@ -487,23 +518,19 @@ bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImag
ret = loadRAW(path, info, rawImage);
qDebug() << "LoadRAW" << timer.elapsed();
}
else if(path.endsWith(".FIT", Qt::CaseInsensitive) || path.endsWith(".FITS", Qt::CaseInsensitive))
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);
ret = loadFITS(path, info, rawImage, planar);
qDebug() << "LoadFITS" << timer.elapsed();
}
else if(path.endsWith(".XISF", Qt::CaseInsensitive))
{
ret = loadXISF(path, info, rawImage);
ret = loadXISF(path, info, rawImage, planar);
qDebug() << "LoadXISF" << timer.elapsed();
}
else
{
QImage img(path);
#ifdef COLOR_MANAGMENT
if(img.colorSpace().isValid() && img.colorSpace() != QColorSpace::SRgb)
img.convertToColorSpace(QColorSpace::SRgb);
#endif
ExifData *exif = exif_data_new_from_file(path.toLocal8Bit().constData());
info.info.append({QObject::tr("Width"), QString::number(img.width())});
@@ -522,8 +549,8 @@ bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImag
}
ConvertRunable::ConvertRunable(const QString &in, const QString &out, const QString &format, const ConvertParams &params, QSemaphore *semaphore) :
m_infile(in),
m_outfile(out),
m_infile(makeMaxPath(in)),
m_outfile(makeMaxPath(out)),
m_format(format),
m_params(params),
m_semaphore(semaphore)
@@ -699,6 +726,7 @@ void ConvertRunable::run()
{
QImage::Format format = QImage::Format_Invalid;
int width = rawimage->widthBytes();
switch(rawimage->type())
{
case RawImage::UINT8:
@@ -712,9 +740,18 @@ void ConvertRunable::run()
else if(rawimage->channels() == 4)format = QImage::Format_RGBA64;
width *= 2;
break;
default:
return;
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);
+2 -1
View File
@@ -8,9 +8,10 @@
class RawImage;
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 loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar = false);
class Image;
+26 -5
View File
@@ -3,7 +3,6 @@
#include <QSurfaceFormat>
#include <QTranslator>
#include <stdlib.h>
#include "libxisf.h"
int main(int argc, char *argv[])
{
@@ -11,11 +10,33 @@ int main(int argc, char *argv[])
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;
format.setMajorVersion(3);
format.setMinorVersion(3);
//format.setOption(QSurfaceFormat::DebugContext);
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
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);
+133 -46
View File
@@ -19,6 +19,9 @@
#include <QStatusBar>
#include <QImageReader>
#include <QMimeDatabase>
#include <QDesktopServices>
#include <QJsonDocument>
#include <QNetworkReply>
#include "loadrunable.h"
#include "markedfiles.h"
#include "about.h"
@@ -59,9 +62,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
_openFilter.append(" ");
nameFilter.append(mimeType.suffixes());
}
_openFilter.append("*.fit *.fits *.xisf *.cr2 *.cr3 *.nef *.dng)");
_openFilter.append("*.fit *.fits *.fts *.fz *.xisf *.cr2 *.cr3 *.nef *.dng)");
_openFilter.append(tr(";;All files (*)"));
nameFilter.append({"fit", "fits", "xisf", "cr2", "cr3", "nef", "dng"});
nameFilter.append({"fit", "fits", "fts", "fz", "xisf", "cr2", "cr3", "nef", "dng"});
QImageReader::setAllocationLimit(0);
m_info = new ImageInfo(this);
@@ -69,26 +72,26 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
infoDock->setWidget(m_info);
infoDock->setObjectName("infoDock");
addDockWidget(Qt::LeftDockWidgetArea, infoDock);
resize(800, 600);
resize(1024, 600);
setStatusBar(new QStatusBar(this));
m_database = new Database(this);
if(!m_database->init())
QMessageBox::critical(this, tr("Can't open DB"), tr("Can't open SQLITE database"));
m_imageGL = new ImageScrollAreaGL(m_database, this);
setCentralWidget(m_imageGL);
m_image = new ImageScrollArea(m_database, this);
setCentralWidget(m_image);
StatusBar *statusBar = new StatusBar(this);
setStatusBar(statusBar);
connect(m_imageGL->imageWidget(), &ImageWidget::status, statusBar, &StatusBar::newStatus);
connect(m_image, &ImageScrollArea::status, statusBar, &StatusBar::newStatus);
m_stretchPanel = new StretchToolbar(this);
connect(m_stretchPanel, &StretchToolbar::paramChanged, m_imageGL->imageWidget(), &ImageWidget::setMTFParams);
connect(m_stretchPanel, &StretchToolbar::paramChanged, m_image, &ImageScrollArea::setMTFParams);
connect(m_stretchPanel, &StretchToolbar::autoStretch, [&](){ m_stretchPanel->stretchImage(m_ringList->currentImage().get()); });
connect(m_stretchPanel, &StretchToolbar::invert, m_imageGL->imageWidget(), &ImageWidget::invert);
connect(m_stretchPanel, &StretchToolbar::superPixel, m_imageGL->imageWidget(), &ImageWidget::superPixel);
connect(m_stretchPanel, &StretchToolbar::falseColor, m_imageGL->imageWidget(), &ImageWidget::falseColor);
connect(m_stretchPanel, &StretchToolbar::invert, m_image, &ImageScrollArea::invert);
connect(m_stretchPanel, &StretchToolbar::superPixel, m_image, &ImageScrollArea::superPixel);
connect(m_stretchPanel, &StretchToolbar::falseColor, m_image, &ImageScrollArea::falseColor);
m_ringList = new ImageRingList(m_database, nameFilter, this);
m_filesystem = new FilesystemWidget(m_ringList, this);
@@ -105,6 +108,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
m_databaseView = new DataBaseView(m_database, this);
connect(m_databaseView, SIGNAL(loadFile(QString)), this, SLOT(loadFile(QString)));
#ifdef PLATESOLVER
_plateSolving = new PlateSolving(this);
addDockWidget(Qt::RightDockWidgetArea, _plateSolving);
_plateSolving->hide();
#endif
addToolBar(Qt::TopToolBarArea, m_stretchPanel);
QDockWidget *filesystemDock = new QDockWidget(tr("Filesystem"), this);
@@ -133,19 +142,22 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
setWindowTitle(tr("Tenmon"));
connect(m_ringList, SIGNAL(pixmapLoaded(Image*)), this, SLOT(pixmapLoaded(Image*)));
connect(m_ringList, SIGNAL(currentImageChanged(int)), this, SLOT(updateWindowTitle()));
connect(m_ringList, SIGNAL(infoLoaded(ImageInfoData)), m_info, SLOT(setInfo(const ImageInfoData&)));
connect(m_ringList, SIGNAL(currentImageChanged(int)), m_filesystem, SLOT(selectFile(int)));
connect(m_ringList, &ImageRingList::thumbnailLoaded, m_imageGL->imageWidget(), &ImageWidget::thumbnailLoaded);
connect(m_ringList, &ImageRingList::pixmapLoaded, m_image, &ImageScrollArea::imageLoaded);
connect(m_ringList, &ImageRingList::currentImageChanged, this, &MainWindow::updateWindowTitle);
connect(m_ringList, &ImageRingList::infoLoaded, m_info, &ImageInfo::setInfo);
connect(m_ringList, &ImageRingList::currentImageChanged, m_filesystem, &FilesystemWidget::selectFile);
connect(m_ringList, &ImageRingList::thumbnailLoaded, m_image, &ImageScrollArea::thumbnailLoaded);
connect(m_ringList, &ImageRingList::pixmapLoaded, m_stretchPanel, &StretchToolbar::imageLoaded);
connect(m_ringList, &ImageRingList::pixmapLoaded, histogram, &Histogram::imageLoaded);
connect(m_imageGL->imageWidget(), &ImageWidget::fileDropped, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
#ifdef PLATESOLVER
connect(m_ringList, &ImageRingList::pixmapLoaded, _plateSolving, &PlateSolving::imageLoaded);
#endif
connect(m_image, &ImageScrollArea::fileDropped, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
QMenu *fileMenu = new QMenu(tr("File"), this);
fileMenu->addAction(tr("Open"), QKeySequence::Open, this, SLOT(loadFile()));
fileMenu->addAction(tr("Open directory recursively"), this, &MainWindow::loadDir);
fileMenu->addAction(tr("Save as"), QKeySequence::Save, this, SLOT(saveAs()));
QAction *saveAs = fileMenu->addAction(tr("Save as"), QKeySequence::Save, this, SLOT(saveAs()));
fileMenu->addSeparator();
fileMenu->addAction(tr("Copy marked files"), Qt::Key_F5, this, SLOT(copyMarked()));
fileMenu->addAction(tr("Move marked files"), Qt::Key_F6, this, SLOT(moveMarked()));
@@ -171,10 +183,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
menuBar()->addMenu(editMenu);
QMenu *viewMenu = new QMenu(tr("View"), this);
viewMenu->addAction(tr("Zoom In"), QKeySequence::ZoomIn, m_imageGL, SLOT(zoomIn()));
viewMenu->addAction(tr("Zoom Out"), QKeySequence::ZoomOut, m_imageGL, SLOT(zoomOut()));
viewMenu->addAction(tr("Best Fit"), QKeySequence("Ctrl+1"), m_imageGL, SLOT(bestFit()));
viewMenu->addAction(tr("100%"), m_imageGL, SLOT(oneToOne()));
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("Best Fit"), QKeySequence("Ctrl+1"), m_image, &ImageScrollArea::bestFit);
viewMenu->addAction(tr("100%"), m_image, &ImageScrollArea::oneToOne);
viewMenu->addSeparator();
QMenu *bayerMenu = viewMenu->addMenu(tr("Bayer mask"));
QActionGroup *bayerActionGroup = new QActionGroup(this);
@@ -190,15 +202,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
viewMenu->addMenu(bayerMenu);
connect(bayerActionGroup, &QActionGroup::triggered, [this](QAction *action){
int data = action->data().toInt();
m_imageGL->imageWidget()->setBayerMask(data);
m_image->setBayerMask(data);
QSettings settings;
settings.setValue("mainwindow/bayermask", data);
});
QAction *thumbnailsAction = viewMenu->addAction(tr("Thumbnails"), Qt::Key_F2, [this](bool checked){
if(SettingsDialog::loadThumbsizes())m_ringList->clearThumbnails();
m_imageGL->imageWidget()->allocateThumbnails(m_ringList->imageNames());
m_imageGL->imageWidget()->showThumbnail(checked);
m_image->allocateThumbnails(m_ringList->imageNames());
m_image->showThumbnail(checked);
if(checked)m_ringList->loadThumbnails();
else m_ringList->stopLoading();
});
@@ -213,8 +225,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
selectMenu->addSeparator();
selectMenu->addAction(tr("Mark and next"), Qt::Key_M, this, SLOT(markAndNext()));
selectMenu->addAction(tr("Unmark and next"), Qt::Key_X, this, SLOT(unmarkAndNext()));
selectMenu->addAction(tr("Show marked"), this, &MainWindow::showMarkFilesDialog);
selectMenu->addSeparator();
selectMenu->addAction(tr("Show marked list"), this, &MainWindow::showMarkFilesDialog);
QAction *openMarked = selectMenu->addAction(tr("Open marked"), m_ringList, &ImageRingList::setMarked);
menuBar()->addMenu(selectMenu);
fileMenu->insertAction(saveAs, openMarked);
/*QMenu *analyzeMenu = new QMenu(tr("Analyze"), this);
QActionGroup *analyzeGroup = new QActionGroup(this);
@@ -248,12 +263,16 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
dockMenu->addAction(databaseViewDock->toggleViewAction());
dockMenu->addAction(filetreeDock->toggleViewAction());
dockMenu->addAction(histogramDock->toggleViewAction());
#ifdef PLATESOLVER
dockMenu->addAction(_plateSolving->toggleViewAction());
#endif
menuBar()->addMenu(dockMenu);
QMenu *helpMenu = menuBar()->addMenu(tr("Help"));
helpMenu->addAction(tr("Help"), QKeySequence::HelpContents, [this]{ HelpDialog help(this); help.exec(); });
helpMenu->addAction(tr("About Tenmon"), [this]{ About about(this); about.exec(); });
helpMenu->addAction(tr("About Qt"), [this](){ QMessageBox::aboutQt(this); });
helpMenu->addAction(tr("Check for update"), this, &MainWindow::checkNewVersion);
setupSigterm();
QSettings settings;
@@ -283,7 +302,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
args.removeFirst();
for(auto &arg : args)
{
QFileInfo info(arg);
QUrl url(arg);
QFileInfo info(url.isLocalFile() ? url.toLocalFile() : arg);
if(info.exists())
{
m_ringList->setFile(info.canonicalFilePath());
@@ -294,7 +314,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
}
}
m_imageGL->setFocus();
m_image->setFocus();
// workaround for nasty wayland backend bug https://bugreports.qt.io/browse/QTBUG-87332
if(static_cast<QGuiApplication*>(QCoreApplication::instance())->platformName() == "wayland")
@@ -387,6 +407,9 @@ void MainWindow::copyOrMove(bool copy, const QString &dest)
if(!dest.isEmpty() && dir.exists())
{
int i = 0;
int missing = 0;
bool overwriteAll = false;
bool skipAll = false;
QStringList files = m_database->getMarkedFiles();
QProgressDialog progress(copy ? tr("Copying") : tr("Moving"), tr("Cancel"), 0, files.size(), this);
progress.setWindowModality(Qt::WindowModal);
@@ -398,8 +421,42 @@ void MainWindow::copyOrMove(bool copy, const QString &dest)
QFile srcFile(file);
QFile dstFile(dir.absoluteFilePath(info.fileName()));
if(dstFile.exists())
if(!srcFile.exists())
{
missing++;
continue;
}
if(dstFile.exists())
{
if(skipAll)
{
continue;
}
else if(overwriteAll)
{
dstFile.remove();
}
else
{
QMessageBox::StandardButton button = QMessageBox::question(this, tr("Overwrite file?"), tr("Destination file %1 already exists. Overwrite?").arg(dstFile.fileName()),
QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll);
switch (button)
{
case QMessageBox::YesToAll:
overwriteAll = true;
case QMessageBox::Yes:
dstFile.remove();
break;
case QMessageBox::NoToAll:
skipAll = true;
case QMessageBox::No:
continue;
default:
break;
}
}
}
if(progress.wasCanceled())
return;
@@ -438,6 +495,8 @@ void MainWindow::copyOrMove(bool copy, const QString &dest)
progress.setValue(i++);
}
m_database->clearMarkedFiles();
if(missing)
QMessageBox::information(this, tr("Missing marked files"), tr("%1 marked files were missing. They were skipped.").arg(missing));
}
}
@@ -450,14 +509,6 @@ void MainWindow::socketNotify()
socketNotifier->setEnabled(true);
}
void MainWindow::pixmapLoaded(Image *image)
{
if(image->rawImage())
{
m_imageGL->setImage(image);
}
}
void MainWindow::loadFile()
{
QString file = QFileDialog::getOpenFileName(this,
@@ -536,27 +587,27 @@ void MainWindow::saveAs()
auto filterToFormat = [](const QString &file, const QString &filter) -> const char*
{
QString suffix = QFileInfo(file).suffix();
if(!suffix.compare("jpg", Qt::CaseInsensitive) || !suffix.compare("jpeg", Qt::CaseInsensitive))return "JPEG";
if(!suffix.compare("png", Qt::CaseInsensitive))return "PNG";
if(!suffix.compare("fits", Qt::CaseInsensitive) || !suffix.compare("fit", Qt::CaseInsensitive))return "FITS";
if(!suffix.compare("xisf", Qt::CaseInsensitive))return "XISF";
if(filter.contains("png"))return "PNG";
if(filter.contains("fits"))return "FITS";
if(filter.contains("xisf"))return "XISF";
return "JPEG";
if(!suffix.compare("jpg", Qt::CaseInsensitive) || !suffix.compare("jpeg", Qt::CaseInsensitive))return "jpeg";
if(!suffix.compare("png", Qt::CaseInsensitive))return "png";
if(!suffix.compare("fits", Qt::CaseInsensitive) || !suffix.compare("fit", Qt::CaseInsensitive))return "fits";
if(!suffix.compare("xisf", Qt::CaseInsensitive))return "xisf";
if(filter.contains("png"))return "png";
if(filter.contains("fits"))return "fits";
if(filter.contains("xisf"))return "xisf";
return "jpeg";
};
if(!file.isEmpty())
{
QString format = filterToFormat(file, selectedFilter);
if(format == "FITS" || format == "XISF")
if(format == "fits" || format == "xisf")
{
convert(file, format);
}
else
{
QImage img = m_imageGL->imageWidget()->renderToImage();
QImage img = m_image->renderToImage();
if(!img.isNull())
img.save(file, filterToFormat(file, selectedFilter));
}
@@ -699,6 +750,42 @@ void MainWindow::exportCSV()
m_databaseView->exportCSV(file);
}
void MainWindow::checkNewVersion()
{
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
QNetworkRequest request(QUrl("https://gitea.nouspiro.space/api/v1/repos/nou/tenmon/releases/latest"));
request.setRawHeader("accept", "application/json");
QNetworkReply *reply = manager->get(request);
connect(reply, &QNetworkReply::finished, [this, manager, reply](){
QJsonParseError error;
QJsonDocument json = QJsonDocument::fromJson(reply->readAll(), &error);
if(json.isObject() && json.object().contains("tag_name"))
{
QString tag = json.object().value("tag_name").toString();
QString version = getVersion();
if(version >= tag)
QMessageBox::information(this, tr("Update check"), tr("You have newest version"));
else
{
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());
qDebug() << url;
if(url.host() == "gitea.nouspiro.space")
QDesktopServices::openUrl(url);
}
}
}
else
{
QMessageBox::warning(this, tr("Update check"), tr("Failed to check version"));
}
reply->deleteLater();
manager->deleteLater();
});
}
void MainWindow::updateWindowTitle()
{
ImagePtr ptr = m_ringList->currentImage();
+4 -4
View File
@@ -4,19 +4,18 @@
#include <QMainWindow>
#include <QSocketNotifier>
#include "imageringlist.h"
#include "imagescrollarea.h"
#include "database.h"
#include "imageinfo.h"
#include "imagescrollareagl.h"
#include "imagescrollarea.h"
#include "filesystemwidget.h"
#include "stretchtoolbar.h"
#include "databaseview.h"
#include "platesolving.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
ImageScrollArea *m_image;
ImageScrollAreaGL *m_imageGL;
ImageRingList *m_ringList;
StretchToolbar *m_stretchPanel;
Database *m_database;
@@ -24,6 +23,7 @@ class MainWindow : public QMainWindow
FilesystemWidget *m_filesystem;
Filetree *m_filetree;
DataBaseView *m_databaseView;
PlateSolving *_plateSolving;
static int socketPair[2];
QSocketNotifier *socketNotifier;
QString _lastDir;
@@ -44,7 +44,6 @@ protected:
protected slots:
void socketNotify();
void updateWindowTitle();
void pixmapLoaded(Image *image);
void loadFile();
void loadFile(const QString &path);
void loadFile(int row);
@@ -68,6 +67,7 @@ protected slots:
void showMarkFilesDialog();
void showSettingsDialog();
void exportCSV();
void checkNewVersion();
};
#endif // MAINWINDOW_H
+1 -1
View File
@@ -60,6 +60,6 @@ void MarkedFiles::clearSelected()
void MarkedFiles::clearAll()
{
QSqlDatabase db = QSqlDatabase::database();
db.exec("DELETE FROM files");
QSqlQuery("DELETE FROM files", db);
m_model->select();
}
+202
View File
@@ -0,0 +1,202 @@
#include "platesolving.h"
#include <QSettings>
#include <QMessageBox>
#include "ui_platesolving.h"
#include "solver.h"
#include "imageringlist.h"
#include "platesolvingsettings.h"
PlateSolving::PlateSolving(QWidget *parent)
: QDockWidget(parent)
, _ui(new Ui::PlateSolving)
{
_ui->setupUi(this);
_solver = new Solver(this);
QSettings settings;
_solver->setIndexFolder(settings.value("platesolving/indexPath", Solver::getTenmonIndexPath()).toString());
auto profiles = StellarSolver::getBuiltInProfiles();
int profileIdx = settings.value("platesolving/profile", 0).toInt();
_solver->setParameters(profiles[profileIdx]);
for(auto &profile : profiles)
{
_ui->profileComboBox->addItem(profile.listName);
}
_ui->profileComboBox->setCurrentIndex(profileIdx);
_ui->profileComboBox->setToolTip(profiles[profileIdx].description);
_ui->scaleUnit->setCurrentIndex(settings.value("platesolving/scaleUnit", 1).toInt());
connect(_ui->profileComboBox, &QComboBox::currentIndexChanged, [this](int index){
auto profiles = StellarSolver::getBuiltInProfiles();
_solver->setParameters(profiles[index]);
_ui->profileComboBox->setToolTip(profiles[index].description);
QSettings settings;
settings.setValue("platesolving/profile", index);
});
connect(_ui->extractButton, &QPushButton::clicked, this, &PlateSolving::extract);
connect(_ui->solveButton, &QPushButton::clicked, this, &PlateSolving::solve);
connect(_ui->settingsButton, &QPushButton::clicked, this, &PlateSolving::settings);
connect(_ui->abortButton, &QPushButton::clicked, this, &PlateSolving::abort);
connect(_ui->updateButton, &QPushButton::clicked, this, &PlateSolving::updateHeader);
connect(_ui->raStart, &QDoubleSpinBox::valueChanged, [this](double val){ _ui->raLabel->setText("RA " + SkyPoint::toHMS(val)); });
connect(_solver, &Solver::solvingDone, this, &PlateSolving::solvingDone);
connect(_solver, &Solver::extractionDone, this, &PlateSolving::extractionDone);
connect(_solver, &Solver::logOutput, [this](const QString &log){ _ui->log->appendPlainText(log); });
}
PlateSolving::~PlateSolving()
{
QSettings settings;
settings.setValue("platesolving/profile", _ui->profileComboBox->currentIndex());
settings.setValue("platesolving/scaleUnit", _ui->scaleUnit->currentIndex());
delete _ui;
}
void PlateSolving::extract()
{
if(!_rawImage)return;
_ui->solveButton->setDisabled(true);
_ui->extractButton->setDisabled(true);
_ui->log->clear();
_solver->loadImage(_rawImage, _path);
_solvingTime.start();
_solver->extractSources(_ui->withHFR->isChecked());
}
void PlateSolving::extractionDone()
{
auto stars = _solver->getStars();
float a = 0;
float b = 0;
float hfr = 0;
for(auto &star : stars)
{
a += star.a;
b += star.b;
hfr += star.HFR;
}
if(size_t size = stars.size())
{
a /= size;
b /= size;
hfr /= size;
}
_ui->stars->setText(QString::number(stars.size()));
_ui->hfr->setText(QString("%1pix Ecc:%2").arg(hfr).arg(std::sqrt(1 - (b*b)/(a*a))));
_ui->log->appendPlainText(QString("Extraction finished in %1 ms").arg(_solvingTime.elapsed()));
_ui->solveButton->setDisabled(false);
_ui->extractButton->setDisabled(false);
}
void PlateSolving::solve()
{
if(!_rawImage)return;
_ui->solveButton->setDisabled(true);
_ui->extractButton->setDisabled(true);
_ui->log->clear();
_solver->loadImage(_rawImage, _path);
if(_ui->usePosition->isChecked())
_solver->setSearchPosition(_ui->raStart->value(), _ui->decStart->value());
if(_ui->useScale->isChecked())
{
SSolver::ScaleUnits scaleUnit;
switch(_ui->scaleUnit->currentIndex())
{
default:
case 0:
scaleUnit = SSolver::ScaleUnits::DEG_WIDTH; break;
case 1:
scaleUnit = SSolver::ScaleUnits::ARCMIN_WIDTH; break;
case 2:
scaleUnit = SSolver::ScaleUnits::ARCSEC_PER_PIX; break;
case 3:
scaleUnit = SSolver::ScaleUnits::FOCAL_MM; break;
}
_solver->setSearchScale(_ui->fovLow->value(), _ui->fovHigh->value(), scaleUnit);
}
_solvingTime.start();
_solver->solveImage();
}
void PlateSolving::solvingDone()
{
_ui->solveButton->setDisabled(false);
_ui->extractButton->setDisabled(false);
auto solution = _solver->getSolution();
_ui->ra->setText(SkyPoint::toHMS(solution.ra / 15.0));
_ui->dec->setText(SkyPoint::toDMS(solution.dec));
_ui->orientation->setText(QString::number(solution.orientation) + "°");
_ui->fieldWidth->setText(QString::number(solution.fieldWidth) + "'");
_ui->fieldHeight->setText(QString::number(solution.fieldHeight) + "\"");
_ui->pixelScale->setText(QString::number(solution.pixscale) + "\"/pix");
_ui->log->appendPlainText(QString("Solving finished in %1 ms").arg(_solvingTime.elapsed()));
}
void PlateSolving::abort()
{
_solver->abort();
}
void PlateSolving::updateHeader()
{
QString error;
if(!_solver->updateHeader(error))
QMessageBox::warning(this, tr("Header update failed"), error);
}
void PlateSolving::imageLoaded(Image *image)
{
if(image && image->rawImage())
{
_rawImage = image->rawImage();
_path = image->name();
_ui->ra->clear();
_ui->dec->clear();
_ui->orientation->clear();
_ui->fieldWidth->clear();
_ui->fieldHeight->clear();
_ui->pixelScale->clear();
_ui->hfr->clear();
_ui->stars->clear();
const ImageInfoData &info = image->info();
SkyPointScale pointScale = info.getCenterRaDec();
if(!std::isnan(pointScale.point.RA()) && !std::isnan(pointScale.point.DEC()))
{
_ui->raStart->setValue(pointScale.point.RAHour());
_ui->decStart->setValue(pointScale.point.DEC());
_ui->usePosition->setChecked(true);
}
else
{
_ui->usePosition->setChecked(false);
}
if(pointScale.scaleValid)
{
_ui->scaleUnit->setCurrentIndex(2);
_ui->fovLow->setValue(pointScale.scaleLow);
_ui->fovHigh->setValue(pointScale.scaleHigh);
_ui->useScale->setChecked(true);
}
else
{
_ui->useScale->setChecked(false);
}
}
}
void PlateSolving::settings()
{
PlateSolvingSettings settings(this);
settings.exec();
_solver->setIndexFolder(settings.indexDirectory());
}
+39
View File
@@ -0,0 +1,39 @@
#ifndef PLATESOLVING_H
#define PLATESOLVING_H
#include "qelapsedtimer.h"
#include <QDockWidget>
class Solver;
class RawImage;
class Image;
namespace Ui {
class PlateSolving;
}
class PlateSolving : public QDockWidget
{
Q_OBJECT
Solver *_solver;
std::shared_ptr<RawImage> _rawImage;
QString _path;
QElapsedTimer _solvingTime;
public:
explicit PlateSolving(QWidget *parent = nullptr);
~PlateSolving();
public slots:
void extract();
void extractionDone();
void solve();
void solvingDone();
void abort();
void updateHeader();
void imageLoaded(Image *image);
void settings();
private:
Ui::PlateSolving *_ui;
};
#endif // PLATESOLVING_H
+364
View File
@@ -0,0 +1,364 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PlateSolving</class>
<widget class="QDockWidget" name="PlateSolving">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>860</width>
<height>700</height>
</rect>
</property>
<property name="windowTitle">
<string>Plate Solving</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Profile</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="profileComboBox"/>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Start point</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="3">
<widget class="QComboBox" name="scaleUnit">
<item>
<property name="text">
<string>Degree width</string>
</property>
</item>
<item>
<property name="text">
<string>Arcmin width</string>
</property>
</item>
<item>
<property name="text">
<string>Arcsec per pixel</string>
</property>
</item>
<item>
<property name="text">
<string>35 mm equivalent focal length</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="usePosition">
<property name="text">
<string>Use position</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QDoubleSpinBox" name="fovHigh">
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>100000.000000000000000</double>
</property>
<property name="stepType">
<enum>QAbstractSpinBox::AdaptiveDecimalStepType</enum>
</property>
<property name="value">
<double>10000.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_11">
<property name="text">
<string>DEC</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="raStart">
<property name="suffix">
<string> h</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>24.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="label_13">
<property name="text">
<string>High</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="fovLow">
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="stepType">
<enum>QAbstractSpinBox::AdaptiveDecimalStepType</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="useScale">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Use scale</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="4">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QDoubleSpinBox" name="decStart">
<property name="suffix">
<string> deg</string>
</property>
<property name="minimum">
<double>-90.000000000000000</double>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Low </string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="raLabel">
<property name="text">
<string>RA</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Unit</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Solution</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>RA</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="ra">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>DEC</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="dec">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Field width</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="fieldWidth">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Field height</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QLineEdit" name="fieldHeight">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Orientation</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="orientation">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Pixel scale</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QLineEdit" name="pixelScale">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Stars</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="stars">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="label_9">
<property name="text">
<string>HFR</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QLineEdit" name="hfr">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="log">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="1">
<widget class="QPushButton" name="settingsButton">
<property name="text">
<string>Settings</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QPushButton" name="extractButton">
<property name="text">
<string>Extract</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="solveButton">
<property name="text">
<string>Solve</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="withHFR">
<property name="text">
<string>Extract with HFR</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QPushButton" name="abortButton">
<property name="text">
<string>Abort</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="updateButton">
<property name="text">
<string>Update FITS header</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
+135
View File
@@ -0,0 +1,135 @@
#include "platesolvingsettings.h"
#include "ui_platesolvingsettings.h"
#include <QSettings>
#include <QFileDialog>
#include "solver.h"
PlateSolvingSettings::PlateSolvingSettings(QWidget *parent) : QDialog(parent)
, _ui(new Ui::PlateSolvingSettings)
{
_ui->setupUi(this);
_download = new HttpDownloader(this);
connect(_download, &HttpDownloader::progress, this, &PlateSolvingSettings::progress);
connect(_ui->stopDownloadButton, &QPushButton::clicked, _download, &HttpDownloader::abort);
connect(_ui->scale01, &QCheckBox::clicked, [this](){ if(_ui->scale01->isChecked())_download->downloadIndex(1); });
connect(_ui->scale02, &QCheckBox::clicked, [this](){ if(_ui->scale02->isChecked())_download->downloadIndex(2); });
connect(_ui->scale03, &QCheckBox::clicked, [this](){ if(_ui->scale03->isChecked())_download->downloadIndex(3); });
connect(_ui->scale04, &QCheckBox::clicked, [this](){ if(_ui->scale04->isChecked())_download->downloadIndex(4); });
connect(_ui->scale05, &QCheckBox::clicked, [this](){ if(_ui->scale05->isChecked())_download->downloadIndex(5); });
connect(_ui->scale06, &QCheckBox::clicked, [this](){ if(_ui->scale06->isChecked())_download->downloadIndex(6); });
connect(_ui->scale07, &QCheckBox::clicked, [this](){ if(_ui->scale07->isChecked())_download->downloadIndex(7); });
connect(_ui->scale08, &QCheckBox::clicked, [this](){ if(_ui->scale08->isChecked())_download->downloadIndex(8); });
connect(_ui->scale09, &QCheckBox::clicked, [this](){ if(_ui->scale09->isChecked())_download->downloadIndex(9); });
connect(_ui->scale10, &QCheckBox::clicked, [this](){ if(_ui->scale10->isChecked())_download->downloadIndex(10); });
connect(_ui->scale11, &QCheckBox::clicked, [this](){ if(_ui->scale11->isChecked())_download->downloadIndex(11); });
connect(_ui->scale12, &QCheckBox::clicked, [this](){ if(_ui->scale12->isChecked())_download->downloadIndex(12); });
connect(_ui->scale13, &QCheckBox::clicked, [this](){ if(_ui->scale13->isChecked())_download->downloadIndex(13); });
connect(_ui->scale14, &QCheckBox::clicked, [this](){ if(_ui->scale14->isChecked())_download->downloadIndex(14); });
connect(_ui->scale15, &QCheckBox::clicked, [this](){ if(_ui->scale15->isChecked())_download->downloadIndex(15); });
connect(_ui->scale16, &QCheckBox::clicked, [this](){ if(_ui->scale16->isChecked())_download->downloadIndex(16); });
connect(_ui->scale17, &QCheckBox::clicked, [this](){ if(_ui->scale17->isChecked())_download->downloadIndex(17); });
connect(_ui->scale18, &QCheckBox::clicked, [this](){ if(_ui->scale18->isChecked())_download->downloadIndex(18); });
connect(_ui->scale19, &QCheckBox::clicked, [this](){ if(_ui->scale19->isChecked())_download->downloadIndex(19); });
QSettings settings;
_ui->indexPaths->addItems(settings.value("platesolving/indexPaths", Solver::getIndexPaths()).toStringList());
_ui->indexPaths->setCurrentText(settings.value("platesolving/indexPath", Solver::getTenmonIndexPath()).toString());
connect(_ui->addButton, &QPushButton::clicked, [this](){
QString path = QFileDialog::getExistingDirectory(this, tr("Index files directory"), Solver::getTenmonIndexPath());
if(!path.isEmpty())
{
bool contain = false;
for(int i=0; i<_ui->indexPaths->count(); i++)
{
if(path == _ui->indexPaths->itemText(i))
{
contain = true;
break;
}
}
if(!contain)_ui->indexPaths->addItem(path);
}
});
connect(_ui->removeButton, &QPushButton::clicked, [this](){
int current = _ui->indexPaths->currentIndex();
if(current > 0)_ui->indexPaths->removeItem(current);
});
_watcher = new QFileSystemWatcher(this);
_watcher->addPath(Solver::getTenmonIndexPath());
connect(_watcher, &QFileSystemWatcher::directoryChanged, this, &PlateSolvingSettings::checkIndexFiles);
connect(_ui->indexPaths, &QComboBox::currentTextChanged, [this](const QString &text){
_watcher->removePaths(_watcher->directories());
_watcher->addPath(text);
});
connect(_ui->indexPaths, &QComboBox::currentIndexChanged, [this](int index){
_ui->indexFilesGroup->setEnabled(index == 0);
});
checkIndexFiles();
}
PlateSolvingSettings::~PlateSolvingSettings()
{
QSettings settings;
settings.setValue("platesolving/indexPath", _ui->indexPaths->currentText());
QStringList paths;
for(int i=0; i<_ui->indexPaths->count(); i++)
paths.append(_ui->indexPaths->itemText(i));
settings.setValue("platesolving/indexPaths", paths);
delete _ui;
}
void PlateSolvingSettings::checkIndexFiles()
{
QString indexDir = Solver::getTenmonIndexPath() + "/";
auto checkScale = [indexDir](QCheckBox *box, int scale)
{
bool all = true;
QStringList files = HttpDownloader::indexFileNames(scale);
for(auto &file : files)
if(!QFile::exists(indexDir + file))
{
all = false;
break;
}
box->setChecked(all);
if(all)box->setStyleSheet("color: green; font: bold;");
else box->setStyleSheet("");
};
checkScale(_ui->scale01, 1);
checkScale(_ui->scale02, 2);
checkScale(_ui->scale03, 3);
checkScale(_ui->scale04, 4);
checkScale(_ui->scale05, 5);
checkScale(_ui->scale06, 6);
checkScale(_ui->scale07, 7);
checkScale(_ui->scale08, 8);
checkScale(_ui->scale09, 9);
checkScale(_ui->scale10, 10);
checkScale(_ui->scale11, 11);
checkScale(_ui->scale12, 12);
checkScale(_ui->scale13, 13);
checkScale(_ui->scale14, 14);
checkScale(_ui->scale15, 15);
checkScale(_ui->scale16, 16);
checkScale(_ui->scale17, 17);
checkScale(_ui->scale18, 18);
checkScale(_ui->scale19, 19);
}
QString PlateSolvingSettings::indexDirectory() const
{
return _ui->indexPaths->currentText();
}
void PlateSolvingSettings::progress(int percent, int files)
{
_ui->filesRemaining->setText(tr("%1 files").arg(files));
_ui->downloadProgressbar->setValue(percent);
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef PLATESOLVINGSETTINGS_H
#define PLATESOLVINGSETTINGS_H
#include <QDialog>
#include <QFileSystemWatcher>
#include "httpdownloader.h"
namespace Ui {
class PlateSolvingSettings;
}
class PlateSolvingSettings : public QDialog
{
Q_OBJECT
HttpDownloader *_download;
QFileSystemWatcher *_watcher;
public:
explicit PlateSolvingSettings(QWidget *parent = nullptr);
~PlateSolvingSettings();
void checkIndexFiles();
QString indexDirectory() const;
protected slots:
void progress(int percent, int files);
private:
Ui::PlateSolvingSettings *_ui;
};
#endif // PLATESOLVINGSETTINGS_H
+229
View File
@@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PlateSolvingSettings</class>
<widget class="QDialog" name="PlateSolvingSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>787</width>
<height>479</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="indexPaths">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<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="QLabel" name="label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Plate solving need index files in order to solve an image. You can download them here by clicking on check box. It will download them into default location. Or you can reuse &lt;span style=&quot; font-weight:700;&quot;&gt;any&lt;/span&gt; index files (not only listed bellow) from astrometry.net by adding path pointing to them. &lt;a href=&quot;https://astrometrynet.readthedocs.io/en/latest/readme.html#getting-index-files&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;More details about index files.&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;It is required to download index files that cover 100%-50% field of view and recomended 100%-10%. So for images with 70' field of view it is required to download index files in 30'-85' and recomended 4'-85'.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="indexFilesGroup">
<property name="title">
<string>Index files</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="5" column="0">
<widget class="QCheckBox" name="scale14">
<property name="text">
<string>240' - 340'index-4114.fits (1.4 MiB)</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="scale17">
<property name="text">
<string>680' - 1000' index-4117.fits (242 kiB)</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="scale16">
<property name="text">
<string>480' - 680' index-4116.fits (400 kiB)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="scale18">
<property name="text">
<string>1000' - 1400' index-4118.fits (183 kiB)</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="scale15">
<property name="text">
<string>340' - 480' index-4115.fits (723 kiB)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="scale19">
<property name="text">
<string>1400' - 2000' index-4119.fits (141 kiB)</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="scale12">
<property name="text">
<string>120' - 170' index-4112.fits (5.1MiB)</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="scale13">
<property name="text">
<string>170' - 240' index-4113.fits (2.7MiB)</string>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QCheckBox" name="scale11">
<property name="text">
<string>85' - 120' index-4111.fits (9.8 MiB)</string>
</property>
</widget>
</item>
<item row="18" column="0">
<widget class="QCheckBox" name="scale10">
<property name="text">
<string>60' - 85' index-4110.fits (24 MiB)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="scale09">
<property name="text">
<string>42' - 60' index-4109.fits (48 MiB)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="scale08">
<property name="text">
<string>30' - 42' index-4108.fits (91 MiB)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="scale07">
<property name="text">
<string>22' - 30' index-4107.fits (158 MiB)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="scale06">
<property name="text">
<string>16' - 22' index-5206-*.fits (294 MiB)</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="scale05">
<property name="text">
<string>11' - 16' index-5205-*.fits (587 MiB)</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="scale04">
<property name="text">
<string>8' - 11' index-5204-*.fits (1.2 GiB)</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="scale03">
<property name="text">
<string>4.0' - 5.6' index-5203-*.fits (2.3 GiB)</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="scale02">
<property name="text">
<string>5.6' - 8.0' index-5202-*.fits (4.6 GiB)</string>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QCheckBox" name="scale01">
<property name="text">
<string>2.0' - 2.8' index-5201-*.fits (8.9 GiB)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="filesRemaining">
<property name="text">
<string>0 files</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="downloadProgressbar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopDownloadButton">
<property name="text">
<string>Stop download</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+350 -100
View File
@@ -2,12 +2,18 @@
#include <QDebug>
#include <cstring>
#include <QElapsedTimer>
#include <QFloat16>
#include <QColorSpace>
#include <lcms2.h>
using F16 = qfloat16;
int THUMB_SIZE = 128;
int THUMB_SIZE_BORDER = 138;
int THUMB_SIZE_BORDER_Y = 158;
double SATURATION = 0.95;
bool QUALITY_RESIZE = true;
extern bool OpenGLES;
#ifdef __SSE2__
template<typename T, int ch>
@@ -21,6 +27,7 @@ size_t RawImage::typeSize(RawImage::DataType type)
case RawImage::UINT8:
return 1;
case RawImage::UINT16:
case RawImage::FLOAT16:
return 2;
case RawImage::UINT32:
case RawImage::FLOAT32:
@@ -38,7 +45,7 @@ void RawImage::allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type)
m_channels = ch;
m_ch = ch == 3 ? 4 : ch;
m_origType = m_type = type;
m_pixels = std::make_unique<PixelType[]>(m_width * m_height * m_ch * typeSize(type));
m_pixels = std::make_unique<PixelType[]>((size_t)m_width * m_height * m_ch * typeSize(type));
}
RawImage::RawImage()
@@ -53,7 +60,7 @@ RawImage::RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type)
RawImage::RawImage(const RawImage &d)
{
allocate(d.m_width, d.m_height, d.m_channels, d.m_type);
std::memcpy(m_pixels.get(), d.m_pixels.get(), m_width * m_height * m_ch * typeSize(m_type));
std::memcpy(m_pixels.get(), d.m_pixels.get(), (size_t)m_width * m_height * m_ch * typeSize(m_type));
m_stats = d.m_stats;
}
@@ -74,6 +81,7 @@ RawImage::RawImage(RawImage &&d)
RawImage::RawImage(const QImage &img)
{
qDebug() << img;
setICCProfile(img.colorSpace().iccProfile());
if(img.format() == QImage::Format_RGBX8888)
{
allocate(img.width(), img.height(), 3, UINT8);
@@ -110,6 +118,24 @@ RawImage::RawImage(const QImage &img)
for(int i=0; i<img.height(); i++)
std::memcpy(data(i), img.scanLine(i), img.width()*2);
}
else if(img.format() == QImage::Format_RGB32 || img.format() == QImage::Format_ARGB32)
{
allocate(img.width(), img.height(), 4, UINT8);
for(int i=0; i<img.height(); i++)
{
uint32_t *src = (uint32_t*)img.scanLine(i);
uint32_t *dst = (uint32_t*)data(i);
for(int o=0; o<img.width(); o++)
{
uint32_t p = src[o];
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
dst[o] = (p & 0xff000000) | (p >> 16 & 0xff) | (p & 0xff00) | (p << 16 & 0xff0000);
#else
dst[o] = (p >> 24) | (p << 8 & 0xffffff00);
#endif
}
}
}
else
{
QImage tmp = img.convertToFormat(QImage::Format_RGBA8888);
@@ -271,46 +297,6 @@ void RawImage::calcStats()
}
}
void RawImage::rect(int &x, int &y, int w, int h, std::vector<double> &r) const
{
/*r.resize(w*h);
x -= w/2;
y -= h/2;
if(x<0)x = 0;
if(y<0)y = 0;
if(x+w >= m_img.cols)x = m_img.cols-w;
if(y+h >= m_img.rows)y = m_img.rows-h;
cv::Mat roiImg(m_img, cv::Rect(x, y, w, h));
cv::Mat doubleMat;
roiImg.convertTo(doubleMat, CV_64F);
r = std::vector<double>(doubleMat.begin<double>(), doubleMat.end<double>());*/
}
int RawImage::findPeaks(double background, double distance, std::vector<Peak> &peaks) const
{
/*std::vector<std::vector<cv::Point>> contours;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(distance, distance));
cv::Mat img, mask, dilate, locMax, result;
if(m_img.channels() == 1)img = m_img;
else cv::cvtColor(m_img, img, cv::COLOR_RGB2GRAY);
cv::dilate(img, dilate, kernel);
cv::compare(img, dilate, locMax, cv::CMP_GE);
cv::compare(img, cv::Scalar(background), mask, cv::CMP_GT);
cv::bitwise_and(locMax, mask, result);
cv::findContours(result, contours, cv::noArray(), cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
peaks.reserve(contours.size());
for(auto contour : contours)
{
peaks.push_back(Peak(1, contour[0].x, contour[0].y));
}
return peaks.size();*/
}
uint32_t RawImage::width() const
{
return m_width;
@@ -326,9 +312,9 @@ uint32_t RawImage::channels() const
return m_channels;
}
uint32_t RawImage::size() const
uint64_t RawImage::size() const
{
return width()*height();
return (uint64_t)width()*height();
}
RawImage::DataType RawImage::type() const
@@ -338,7 +324,7 @@ RawImage::DataType RawImage::type() const
uint32_t RawImage::norm() const
{
switch(m_type)
switch(m_origType)
{
case UINT8:
return UINT8_MAX;
@@ -368,12 +354,12 @@ const void *RawImage::data() const
void *RawImage::data(uint32_t row, uint32_t col)
{
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
return m_pixels.get() + ((size_t)m_width * row * m_ch + (size_t)col * m_ch) * typeSize(m_type);
}
const void *RawImage::data(uint32_t row, uint32_t col) const
{
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
return m_pixels.get() + ((size_t)m_width * row * m_ch + (size_t)col * m_ch) * typeSize(m_type);
}
const void *RawImage::origData() const
@@ -387,9 +373,13 @@ const void *RawImage::origData() const
const void *RawImage::origData(uint32_t row, uint32_t col) const
{
if(m_original)
return m_original.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_origType);
{
col = (uint64_t)col * m_origWidth / m_width;
row = (uint64_t)row * m_origHeight / m_height;
return m_original.get() + ((size_t)m_origWidth * row * m_ch + (size_t)col * m_ch) * typeSize(m_origType);
}
else
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
return m_pixels.get() + ((size_t)m_width * row * m_ch + (size_t)col * m_ch) * typeSize(m_type);
}
bool RawImage::planar() const
@@ -404,29 +394,29 @@ void RawImage::convertToThumbnail()
if(m_thumbAspect == 0.0f)
m_thumbAspect = (float)width() / height();
std::unique_ptr<PixelType[]> outptr = std::make_unique<PixelType[]>(THUMB_SIZE * THUMB_SIZE * 4 * sizeof(uint16_t));
uint16_t *out = reinterpret_cast<uint16_t*>(outptr.get());
std::unique_ptr<PixelType[]> outptr = std::make_unique<PixelType[]>(THUMB_SIZE * THUMB_SIZE * 4 * sizeof(F16));
F16 *out = reinterpret_cast<F16*>(outptr.get());
auto loop = [&](uint16_t *out, auto *in, auto scale)
auto loop = [&](F16 *out, auto *in, float scale)
{
for(int i=0; i<THUMB_SIZE; i++)
for(int64_t i=0; i<THUMB_SIZE; i++)
{
for(int o=0; o<THUMB_SIZE; o++)
for(int64_t o=0; o<THUMB_SIZE; o++)
{
int idx = (i*THUMB_SIZE + o)*4;
int idx2 = ((i * m_height / THUMB_SIZE * m_width) + (o * m_width / THUMB_SIZE)) * m_ch;
int64_t idx = (i*THUMB_SIZE + o)*4;
int64_t idx2 = ((i * m_height / THUMB_SIZE * m_width) + (o * m_width / THUMB_SIZE)) * m_ch;
if(m_channels == 1)
{
out[idx] = out[idx + 1] = out[idx + 2] = in[idx2] * scale;
out[idx] = out[idx + 1] = out[idx + 2] = (F16)(in[idx2] * scale);
}
else
{
out[idx] = in[idx2] * scale;;
out[idx + 1] = in[idx2 + 1] * scale;;
out[idx + 2] = in[idx2 + 2] * scale;;
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] = UINT16_MAX;
out[idx + 3] = (F16)1.0f;
}
}
};
@@ -434,16 +424,19 @@ void RawImage::convertToThumbnail()
switch(m_type)
{
case UINT8:
loop(out, reinterpret_cast<uint8_t*>(m_pixels.get()), 256);
loop(out, reinterpret_cast<uint8_t*>(m_pixels.get()), 1.0f/UINT8_MAX);
break;
case UINT16:
loop(out, reinterpret_cast<uint16_t*>(m_pixels.get()), 1);
loop(out, reinterpret_cast<uint16_t*>(m_pixels.get()), 1.0f/UINT16_MAX);
break;
case UINT32:
loop(out, reinterpret_cast<uint32_t*>(m_pixels.get()), UINT16_MAX/(float)UINT32_MAX);
loop(out, reinterpret_cast<uint32_t*>(m_pixels.get()), (float)(1.0/UINT32_MAX));
break;
case FLOAT16:
loop(out, reinterpret_cast<F16*>(m_pixels.get()), 1.0f);
break;
case FLOAT32:
loop(out, reinterpret_cast<float*>(m_pixels.get()), 65535.0);
loop(out, reinterpret_cast<float*>(m_pixels.get()), 1.0f);
break;
default:
qWarning() << "FLOAT64 should not happend";
@@ -455,34 +448,112 @@ void RawImage::convertToThumbnail()
m_height = THUMB_SIZE;
m_ch = 4;
m_channels = 3;
m_type = UINT16;
m_type = FLOAT16;
}
void RawImage::convertToGLFormat()
{
size_t s = size() * m_ch;
if(m_type == UINT32)
{
m_original = std::move(m_pixels);
allocate(m_width, m_height, m_channels, FLOAT32);
m_origType = UINT32;
float *dst = reinterpret_cast<float*>(m_pixels.get());
uint32_t *src = reinterpret_cast<uint32_t*>(m_original.get());
if(m_type == UINT32 || m_type == FLOAT64)
convertToType(FLOAT32);
else if(OpenGLES && m_type == UINT16)
convertToType(FLOAT16);
}
for(size_t i = 0; i < s; i++)
dst[i] = src[i] / (float)UINT32_MAX;
}
else if(m_type == FLOAT64)
template<typename T, typename U>
void convertType2(size_t size, const T *src, U *dst)
{
if constexpr((std::is_floating_point_v<T> || std::is_same_v<T, F16>) && (std::is_floating_point_v<U> || std::is_same_v<T, F16>))
{
m_original = std::move(m_pixels);
allocate(m_width, m_height, m_channels, FLOAT32);
m_origType = FLOAT64;
float *dst = reinterpret_cast<float*>(m_pixels.get());
double *src = reinterpret_cast<double*>(m_original.get());
for(size_t i = 0; i < s; i++)
for(size_t i = 0; i < size; i++)
dst[i] = src[i];
}
if constexpr(std::is_integral_v<T> && std::is_integral_v<U>)
{
if constexpr(sizeof(T) > sizeof(U))
for(size_t i = 0; i < size; i++)
dst[i] = src[i] >> ((sizeof(T) - sizeof(U)) * 8);
else
for(size_t i = 0; i < size; i++)
dst[i] = static_cast<U>(src[i]) << ((sizeof(U) - sizeof(T)) * 8);
}
if constexpr((std::is_floating_point_v<T> || std::is_same_v<T, F16>) && std::is_integral_v<U>)
{
U max = std::numeric_limits<U>::max();
T scale = (T)(max);
for(size_t i = 0; i < size; i++)
dst[i] = src[i] * scale;
}
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());
for(size_t i = 0; i < size; i++)
dst[i] = (U)src[i] * scale;
}
}
template<typename T>
void convertType(size_t size, RawImage::DataType dstType, const T *src, void *dst)
{
switch(dstType)
{
case RawImage::UINT8:
convertType2(size, src, static_cast<uint8_t*>(dst));
break;
case RawImage::UINT16:
convertType2(size, src, static_cast<uint16_t*>(dst));
break;
case RawImage::UINT32:
convertType2(size, src, static_cast<uint32_t*>(dst));
break;
case RawImage::FLOAT16:
convertType2(size, src, static_cast<F16*>(dst));
break;
case RawImage::FLOAT32:
convertType2(size, src, static_cast<float*>(dst));
break;
case RawImage::FLOAT64:
convertType2(size, src, static_cast<double*>(dst));
break;
}
}
void RawImage::convertToType(DataType type)
{
if(type == m_type)
return;
m_origWidth = m_width;
m_origHeight = m_height;
m_original = std::move(m_pixels);
DataType origType = m_type;
allocate(m_width, m_height, m_channels, type);
m_origType = origType;
size_t s = size() * m_ch;
switch(m_origType)
{
case UINT8:
convertType(s, type, reinterpret_cast<uint8_t*>(m_original.get()), m_pixels.get());
break;
case UINT16:
convertType(s, type, reinterpret_cast<uint16_t*>(m_original.get()), m_pixels.get());
break;
case UINT32:
convertType(s, type, reinterpret_cast<uint32_t*>(m_original.get()), m_pixels.get());
break;
case FLOAT16:
convertType(s, type, reinterpret_cast<F16*>(m_original.get()), m_pixels.get());
break;
case FLOAT32:
convertType(s, type, reinterpret_cast<float*>(m_original.get()), m_pixels.get());
break;
case FLOAT64:
convertType(s, type, reinterpret_cast<double*>(m_original.get()), m_pixels.get());
break;
}
}
float RawImage::thumbAspect() const
@@ -571,32 +642,47 @@ bool RawImage::pixel(int x, int y, double &r, double &g, double &b) const
}
break;
}
case FLOAT16:
{
const F16 *v = static_cast<const F16*>(origData(y, x));
if(m_channels == 1)
{
r = g = b = *v;
}
else
{
r = v[0];
g = v[1];
b = v[2];
}
break;
}
}
return true;
}
template<typename T>
template<typename T, typename U = float>
void boxResample(uint32_t w, uint32_t h, uint32_t ch, uint32_t oldw, uint32_t oldh, const uint8_t *in_, uint8_t *out_)
{
if(oldw == 0 || oldh == 0)return;
const T *in = reinterpret_cast<const T*>(in_);
T *out = reinterpret_cast<T*>(out_);
float max = 255.0f;
if constexpr(std::is_same<T, uint16_t>::value)
max = UINT16_MAX;
U max = 255.0f;
if constexpr(std::is_integral_v<T>)
max = (U)std::numeric_limits<T>::max();
float sx = (float)w / oldw;
float sy = (float)h / oldh;
for(uint32_t y = 0; y < h; y++)//iterate over destination Y
for(uint64_t y = 0; y < h; y++)//iterate over destination Y
{
for(uint32_t x = 0; x < w; x++)//iterate over destination X
for(uint64_t x = 0; x < w; x++)//iterate over destination X
{
float p[4] = {0.0f};
uint32_t xx = x * oldw / w;//calculate source rect
uint32_t yy = y * oldh / h;
uint32_t xe = std::min((x + 1) * oldw / w, oldw - 1);
uint32_t ye = std::min((y + 1) * oldh / h, oldh - 1);
U p[4] = {0.0f};
uint64_t xx = x * oldw / w;//calculate source rect
uint64_t yy = y * oldh / h;
uint64_t xe = std::min((x + 1) * oldw / w, (uint64_t)oldw - 1);
uint64_t ye = std::min((y + 1) * oldh / h, (uint64_t)oldh - 1);
for(uint32_t o = yy; o <= ye; o++)//iterate over source Y
{
float cy = o * sy - y;
@@ -616,8 +702,8 @@ void boxResample(uint32_t w, uint32_t h, uint32_t ch, uint32_t oldw, uint32_t ol
}
}
for(uint32_t z = 0; z < ch; z++)
if constexpr(std::is_floating_point<T>::value)
out[(y * w + x) * ch + z] = p[z];
if constexpr(std::is_floating_point_v<T> || std::is_same_v<T, F16>)
out[(y * w + x) * ch + z] = (T)p[z];
else
out[(y * w + x) * ch + z] = std::clamp(std::round(p[z]), 0.0f, max);
}
@@ -633,7 +719,9 @@ void RawImage::resize(uint32_t w, uint32_t h)
uint32_t oldh = m_height;
m_thumbAspect = (float)m_width / m_height;
DataType origType = m_origType;
allocate(w, h, m_channels, m_type);
m_origType = origType;
switch(m_type)
{
@@ -643,11 +731,17 @@ void RawImage::resize(uint32_t w, uint32_t h)
case RawImage::UINT16:
boxResample<uint16_t>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
break;
case RawImage::UINT32:
boxResample<uint32_t>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
break;
case RawImage::FLOAT16:
boxResample<F16>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
break;
case RawImage::FLOAT32:
boxResample<float>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
break;
default:
qWarning() << "Resizing format not supported";
case RawImage::FLOAT64:
boxResample<double, double>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
break;
}
}
@@ -721,6 +815,9 @@ std::shared_ptr<RawImage> RawImage::fromPlanar(const void *pixels, uint32_t w, u
convert(static_cast<const uint16_t*>(pixels), static_cast<uint16_t*>(image->data()), UINT16_MAX);
#endif
break;
case FLOAT16:
convert(static_cast<const F16*>(pixels), static_cast<F16*>(image->data()), (F16)1.0f);
break;
case UINT32:
#ifdef __SSE2__
if(ch==3)
@@ -749,6 +846,39 @@ std::shared_ptr<RawImage> RawImage::fromPlanar(const void *pixels, uint32_t w, u
return image;
}
std::shared_ptr<RawImage> RawImage::toPlanar()
{
std::shared_ptr<RawImage> ret = std::make_shared<RawImage>(m_width, m_height, 1, m_type);
size_t size = m_width * m_height;
size_t ch = m_ch;
auto convert = [&](auto *in, auto *out)
{
for(size_t i=0; i<size; i++)
out[i] = in[i * ch];
};
switch(m_type)
{
case UINT8:
convert(static_cast<uint8_t*>(data()), static_cast<uint8_t*>(ret->data()));
break;
case UINT16:
case FLOAT16:
convert(static_cast<uint16_t*>(data()), static_cast<uint16_t*>(ret->data()));
break;
case UINT32:
case FLOAT32:
convert(static_cast<uint32_t*>(data()), static_cast<uint32_t*>(ret->data()));
break;
case FLOAT64:
convert(static_cast<double*>(data()), static_cast<double*>(ret->data()));
break;
}
return ret;
}
std::vector<RawImage> RawImage::split() const
{
std::vector<RawImage> planes;
@@ -770,6 +900,7 @@ std::vector<RawImage> RawImage::split() const
case UINT8:
extract(static_cast<const uint8_t*>(data()), static_cast<uint8_t*>(planes[i].data()), i);
break;
case FLOAT16:
case UINT16:
extract(static_cast<const uint16_t*>(data()), static_cast<uint16_t*>(planes[i].data()), i);
break;
@@ -790,3 +921,122 @@ bool RawImage::valid() const
{
return m_width > 0 && m_height > 0;
}
void RawImage::setICCProfile(const QByteArray &icc)
{
if(icc.size())
m_iccProfile = std::vector<uint8_t>(icc.begin(), icc.end());
}
void RawImage::setICCProfile(const LibXISF::ByteArray &icc)
{
if(icc.size())
m_iccProfile = std::vector<uint8_t>(icc.data(), icc.data() + icc.size());
}
void RawImage::convertTosRGB()
{
if(m_channels == 1 || m_iccProfile.empty())
return;
cmsUInt32Number type = TYPE_RGBA_8;
switch(m_type)
{
case UINT8:
type = TYPE_RGBA_8;
break;
case UINT16:
type = TYPE_RGBA_16;
break;
case FLOAT16:
type = TYPE_RGBA_HALF_FLT;
break;
case FLOAT32:
type = TYPE_RGBA_FLT;
break;
default:
return;
}
cmsHPROFILE inProfile = cmsOpenProfileFromMem(m_iccProfile.data(), m_iccProfile.size());
cmsHPROFILE outProfile = cmsCreate_sRGBProfile();
if(inProfile && outProfile)
{
cmsHTRANSFORM transform = cmsCreateTransform(inProfile, type, outProfile, type, INTENT_PERCEPTUAL, cmsFLAGS_COPY_ALPHA);
std::unique_ptr<PixelType[]> tmp = std::move(m_pixels);
allocate(m_width, m_height, m_channels, m_type);
if(transform)
{
cmsDoTransform(transform, tmp.get(), m_pixels.get(), size());
cmsDeleteTransform(transform);
}
else
{
qDebug() << "Failed to create color transform";
}
}
else
{
qDebug() << "Failed to open icc profile";
}
cmsCloseProfile(inProfile);
cmsCloseProfile(outProfile);
}
void RawImage::generateLUT()
{
if(m_channels == 1 || m_iccProfile.empty())
return;
const int LUT_SIZE = 32;
const float LUT_SIZEF = LUT_SIZE - 1;
std::vector<F16> lut(LUT_SIZE * LUT_SIZE * LUT_SIZE * 4);
m_lut.resize(lut.size());
for(int z = 0; z < LUT_SIZE; z++)
{
for(int y = 0; y < LUT_SIZE; y++)
{
F16 *line = &lut[(z*LUT_SIZE*LUT_SIZE + y*LUT_SIZE) * 4];
for(int x = 0; x < LUT_SIZE; x++)
{
line[x*4 + 0] = (F16)(x / LUT_SIZEF);
line[x*4 + 1] = (F16)(y / LUT_SIZEF);
line[x*4 + 2] = (F16)(z / LUT_SIZEF);
line[x*4 + 3] = (F16)1.0f;
}
}
}
cmsHPROFILE inProfile = cmsOpenProfileFromMem(m_iccProfile.data(), m_iccProfile.size());
cmsHPROFILE outProfile = cmsCreate_sRGBProfile();
if(inProfile && outProfile)
{
cmsHTRANSFORM transform = cmsCreateTransform(inProfile, TYPE_RGBA_HALF_FLT, outProfile, TYPE_RGBA_HALF_FLT, INTENT_PERCEPTUAL, cmsFLAGS_COPY_ALPHA);
if(transform)
{
cmsDoTransform(transform, &lut[0], &m_lut[0], lut.size()/4);
cmsDeleteTransform(transform);
}
else
{
qDebug() << "Failed to create color transform";
m_lut.clear();
}
}
else
{
qDebug() << "Failed to open icc profile";
m_lut.clear();
}
cmsCloseProfile(inProfile);
cmsCloseProfile(outProfile);
}
const std::vector<uint16_t> &RawImage::getLUT() const
{
return m_lut;
}
+15 -4
View File
@@ -1,8 +1,8 @@
#ifndef RAWIMAGE_H
#define RAWIMAGE_H
#include "libxisf.h"
#include <vector>
#include <algorithm>
#include <memory>
#include <stdint.h>
#include <math.h>
@@ -45,6 +45,7 @@ public:
UINT8,
UINT16,
UINT32,
FLOAT16,
FLOAT32,
FLOAT64,
};
@@ -65,6 +66,8 @@ protected:
std::unique_ptr<PixelType[]> m_original;
uint32_t m_width = 0;
uint32_t m_height = 0;
uint32_t m_origWidth = 0;
uint32_t m_origHeight = 0;
uint32_t m_channels = 0;
uint32_t m_ch = 0;
DataType m_type = UINT8;
@@ -72,6 +75,8 @@ protected:
float m_thumbAspect = 0.0;
Stats m_stats;
bool m_planar = false;
std::vector<uint8_t> m_iccProfile;
std::vector<uint16_t> m_lut;// actually qfloat16
void allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type);
public:
RawImage();
@@ -81,12 +86,10 @@ public:
RawImage(const QImage &img);
const RawImage::Stats& imageStats() const;
void calcStats();
void rect(int &x, int &y, int w, int h, std::vector<double> &r) const;
int findPeaks(double background, double distance, std::vector<Peak> &peaks) const;
uint32_t width() const;
uint32_t height() const;
uint32_t channels() const;
uint32_t size() const;
uint64_t size() const;
DataType type() const;
uint32_t norm() const;
uint32_t widthBytes() const;
@@ -100,6 +103,7 @@ public:
void setPlanar();
void convertToThumbnail();
void convertToGLFormat();
void convertToType(RawImage::DataType type);
float thumbAspect() const;
bool pixel(int x, int y, double &r, double &g, double &b) const;
void resize(uint32_t w, uint32_t h);
@@ -108,9 +112,16 @@ public:
static std::shared_ptr<RawImage> fromPlanar(const RawImage &img);
static std::shared_ptr<RawImage> fromPlanar(const void *pixels, uint32_t w, uint32_t h, uint32_t ch, DataType type);
std::shared_ptr<RawImage> toPlanar();
static size_t typeSize(DataType type);
std::vector<RawImage> split() const;
bool valid() const;
void setICCProfile(const QByteArray &icc);
void setICCProfile(const LibXISF::ByteArray &icc);
void convertTosRGB();
void generateLUT();
const std::vector<uint16_t>& getLUT() const;
};
//Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr);
+6 -6
View File
@@ -21,7 +21,7 @@ void fromPlanarSSE(const void *in, void *out, size_t count)
__m128i r = _mm_loadu_si128(_in[0] + i);
__m128i g = _mm_loadu_si128(_in[1] + i);
__m128i b = _mm_loadu_si128(_in[2] + i);
if constexpr(ch==4)a = _mm_loadu_si128(_in[3]);
if constexpr(ch==4)a = _mm_loadu_si128(_in[3] + i);
__m128i d1 = _mm_unpacklo_epi8(r, b);
__m128i d2 = _mm_unpacklo_epi8(g, a);
@@ -43,7 +43,7 @@ void fromPlanarSSE(const void *in, void *out, size_t count)
__m128i r = _mm_loadu_si128(_in[0] + i);
__m128i g = _mm_loadu_si128(_in[1] + i);
__m128i b = _mm_loadu_si128(_in[2] + i);
if constexpr(ch==4)a = _mm_loadu_si128(_in[3]);
if constexpr(ch==4)a = _mm_loadu_si128(_in[3] + i);
__m128i d1 = _mm_unpacklo_epi16(r, b);
__m128i d2 = _mm_unpacklo_epi16(g, a);
@@ -66,7 +66,7 @@ void fromPlanarSSE(const void *in, void *out, size_t count)
__m128i r = _mm_loadu_si128(_in[0] + i);
__m128i g = _mm_loadu_si128(_in[1] + i);
__m128i b = _mm_loadu_si128(_in[2] + i);
if constexpr(ch==4)a = _mm_loadu_si128(_in[3]);
if constexpr(ch==4)a = _mm_loadu_si128(_in[3] + i);
__m128i d1 = _mm_unpacklo_epi32(r, b);
__m128i d2 = _mm_unpacklo_epi32(g, a);
@@ -84,15 +84,15 @@ void fromPlanarSSE(const void *in, void *out, size_t count)
switch(sizeof(T))
{
case 1:
for(uint32_t o=0; o<ch; o++)static_cast<uint8_t*>(out)[i + o] = static_cast<const uint8_t*>(in)[i + o + s2];
for(uint32_t o=0; o<ch; o++)static_cast<uint8_t*>(out)[i*4 + o] = static_cast<const uint8_t*>(in)[i + o*s2];
if(ch==3)static_cast<uint8_t*>(out)[i*4 + 3] = 0xff;
break;
case 2:
for(uint32_t o=0; o<ch; o++)static_cast<uint16_t*>(out)[i + o] = static_cast<const uint16_t*>(in)[i + o + s2];
for(uint32_t o=0; o<ch; o++)static_cast<uint16_t*>(out)[i*4 + o] = static_cast<const uint16_t*>(in)[i + o*s2];
if(ch==3)static_cast<uint16_t*>(out)[i*4 + 3] = 0xffff;
break;
case 4:
for(uint32_t o=0; o<ch; o++)static_cast<uint32_t*>(out)[i + o] = static_cast<const uint32_t*>(in)[i + o + s2];
for(uint32_t o=0; o<ch; o++)static_cast<uint32_t*>(out)[i*4 + o] = static_cast<const uint32_t*>(in)[i + o*s2];
if(ch==3)
{
if(!std::numeric_limits<T>::is_integer)static_cast<float*>(out)[i*4 + 3] = 1.0;
+1 -1
View File
@@ -3,7 +3,6 @@
<file>invert.png</file>
<file>nuke.png</file>
<file>bayer.png</file>
<file>space.nouspiro.tenmon.png</file>
<file>nuke_a.png</file>
<file>../about/tenmon</file>
<file>../translations/tenmon_en.qm</file>
@@ -16,6 +15,7 @@
<file>bggr.png</file>
<file>grbg.png</file>
<file>gbrg.png</file>
<file>space.nouspiro.tenmon.png</file>
</qresource>
<qresource lang="en" prefix="/">
<file alias="help">../about/help_en</file>
+208 -18
View File
@@ -3,12 +3,16 @@
#include <QFileInfo>
#include <QDebug>
#include <QInputDialog>
#include <QJsonValue>
#include "loadrunable.h"
#include "rawimage.h"
#include "loadrunable.h"
#include "batchprocessing.h"
#include <fitsio2.h>
#include "libXISF/libxisf.h"
#ifdef PLATESOLVER
#include "solver.h"
#endif // PLATESOLVER
namespace Script
{
@@ -25,6 +29,11 @@ ScriptEngine::ScriptEngine(BatchProcessing *parent)
_jsEngine->globalObject().setProperty("FITSRecordModify", fitsRecordObject);
_database->init(QLatin1String("scriptengine"));
_semaphore.release(_pool->maxThreadCount());
_pool->setThreadPriority(QThread::LowPriority);
#ifdef PLATESOLVER
_solver = new Solver(this);
#endif // PLATESOLVER
}
void ScriptEngine::setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir)
@@ -46,6 +55,9 @@ const QString &ScriptEngine::outputDir() const
void ScriptEngine::interrupt()
{
#ifdef PLATESOLVER
if(_solver)_solver->abort();
#endif
_jsEngine->setInterrupted(true);
}
@@ -130,9 +142,9 @@ bool ScriptEngine::convert(File *file, QString &outpath, const QString &format,
path = dir.absoluteFilePath(outpath);
QString f = format.toLower();
if(f != "xisf" && f != "fits" && f != "png" && f != "bmp" && f != "jpg")
if(f != "xisf" && f != "fits" && f != "png" && f != "bmp" && f != "jpg" && f != "tiff")
{
logError("Output format must be one of xisf fits jpg png bmp");
logError("Output format must be one of xisf fits jpg png bmp tiff");
return false;
}
@@ -151,6 +163,159 @@ bool ScriptEngine::convert(File *file, QString &outpath, const QString &format,
return true;
}
#ifdef PLATESOLVER
void ScriptEngine::setSolverProfile(int index)
{
if(_solver && index >= SSolver::Parameters::DEFAULT && index < SSolver::Parameters::BIG_STARS)
{
_solver->setParameters((SSolver::Parameters::ParametersProfile)index);
}
}
void ScriptEngine::setSolverProfile(const QVariantMap &profile)
{
if(_solver)
{
SSolver::Parameters params = SSolver::Parameters::convertFromMap(profile);
_solver->setParameters(params);
}
}
QJSValue ScriptEngine::getSolverProfile() const
{
if(_solver)
{
QMap<QString, QVariant> params = SSolver::Parameters::convertToMap(_solver->getProfile());
QJSValue ret = _jsEngine->newObject();
for(auto i = params.begin(); i != params.end(); i++)
{
switch(i.value().metaType().id())
{
case QMetaType::Int:
ret.setProperty(i.key(), i.value().toInt());
break;
case QMetaType::Double:
ret.setProperty(i.key(), i.value().toDouble());
break;
case QMetaType::Bool:
ret.setProperty(i.key(), i.value().toBool());
break;
case QMetaType::QString:
ret.setProperty(i.key(), i.value().toString());
break;
default:
qWarning() << "unhandled metatype" << i.key() << i.value();
break;
}
}
return ret;
}
else
{
return QJSValue();
}
}
void ScriptEngine::setStartingSolution(const QJSValue &solution)
{
if(solution.isObject())
{
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());
if(solution.hasProperty("pixscale") && solution.property("pixscale").isNumber())
{
double scale = solution.property("pixscale").toNumber();
_solver->setSearchScale(scale * 0.8, scale * 1.2, SSolver::ScaleUnits::ARCSEC_PER_PIX);
}
}
else
{
_solver->clearStartingPositionAndScale();
}
}
QJSValue ScriptEngine::solveImage(File *file, bool updateHeader)
{
QString path = file->absoluteFilePath();
QJSValue ret = newObject();
if(_solver->loadImage(path))
{
if(_solver->solveImage(true))
{
auto solution = _solver->getSolution();
ret.setProperty("fieldWidth", solution.fieldWidth);
ret.setProperty("fieldHeight", solution.fieldHeight);
ret.setProperty("ra", solution.ra);
ret.setProperty("dec", solution.dec);
ret.setProperty("orientation", solution.orientation);
ret.setProperty("pixscale", solution.pixscale);
ret.setProperty("parity", solution.parity == FITSImage::Parity::POSITIVE);
ret.setProperty("raError", solution.raError);
ret.setProperty("decError", solution.decError);
if(updateHeader)
{
QString error;
if(!_solver->updateHeader(error))
logError(error);
}
}
else
{
logError("Failed to plate solve image " + path);
}
}
else
{
logError("Failed to load image " + path);
}
return ret;
}
QJSValue ScriptEngine::extractStars(File *file, bool hfr)
{
QJSValue ret;
QString path = file->absoluteFilePath();
if(_solver->loadImage(path))
{
if(_solver->extractSources(hfr, true))
{
auto stars = _solver->getStars();
ret = newArray(stars.size());
int i = 0;
for(auto &star : stars)
{
QJSValue starj = newObject();
starj.setProperty("x", star.x);
starj.setProperty("y", star.y);
starj.setProperty("mag", star.mag);
starj.setProperty("flux", star.flux);
starj.setProperty("peak", star.peak);
starj.setProperty("HFR", star.HFR);
starj.setProperty("a", star.a);
starj.setProperty("b", star.b);
starj.setProperty("theta", star.theta);
starj.setProperty("ra", star.ra);
starj.setProperty("dec", star.dec);
starj.setProperty("numPixels", star.numPixels);
ret.setProperty(i++, starj);
}
}
else
{
logError("Failed to extract sources from " + path);
}
}
else
{
logError("Failed to load image " + path);
}
return ret;
}
#endif // PLATESOLVER
QJSValue ScriptEngine::newObject()
{
return _jsEngine->newObject();
@@ -350,16 +515,17 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
_fitsKeywordsLoaded = false;
_fitsKeywords.clear();
if(QRegularExpression("fits?", QRegularExpression::CaseInsensitiveOption).match(suffix()).hasMatch())
if(QRegularExpression("(fits?|fz|fts)", QRegularExpression::CaseInsensitiveOption).match(suffix()).hasMatch())
{
fitsfile *file;
int status = 0;
fits_open_diskfile(&file, _path.toLocal8Bit().data(), READWRITE, &status);
QString path = makeMaxPath(_path);
fits_open_diskfile(&file, path.toLocal8Bit().data(), READWRITE, &status);
int num = 0;
fits_get_num_hdus(file, &num, &status);
if(status)
{
_engine->newMessage("Failed to open FITS file", true);
if(_engine)_engine->newMessage("Failed to open FITS file", true);
return false;
}
int imgtype;
@@ -412,7 +578,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
break;
}
default:
_engine->newMessage("Unknown type for KEY " + record.key, true);
if(_engine)_engine->newMessage("Unknown type for KEY " + record.key, true);
return false;
break;
}
@@ -420,7 +586,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
{
char error[100];
fits_get_errstatus(status, error);
_engine->newMessage(QString("Error when updating KEY %1 %2").arg(record.key).arg(error), true);
if(_engine)_engine->newMessage(QString("Error when updating KEY %1 %2").arg(record.key).arg(error), true);
return false;
}
}
@@ -456,7 +622,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
break;
}
default:
_engine->newMessage("Unknown type for KEY " + record.key, true);
if(_engine)_engine->newMessage("Unknown type for KEY " + record.key, true);
return false;
break;
}
@@ -464,7 +630,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
{
char error[100];
fits_get_errstatus(status, error);
_engine->newMessage(QString("Error when adding KEY {} {}").arg(record.key).arg(error), true);
if(_engine)_engine->newMessage(QString("Error when adding KEY {} {}").arg(record.key).arg(error), true);
return false;
}
}
@@ -472,31 +638,37 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
return status == 0;
}
else if(suffix() == "xisf")
else if(suffix().toLower() == "xisf")
{
try
{
LibXISF::XISFModify modifyXISF;
modifyXISF.open(_path.toLocal8Bit().data());
QFileInfo in(_path);
QFileInfo out(_path + "~");
QString in = makeMaxPath(absoluteFilePath());
QString out = in + "~";
modifyXISF.open(in.toLocal8Bit().data());
qDebug() << "modify" << in << out;
for(auto &remove : modify->_remove)
modifyXISF.removeFITSKeyword(0, remove.toStdString());
for(auto &record : modify->_update)
modifyXISF.updateFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.value.toString().toStdString()}, true);
modifyXISF.updateFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()}, true);
for(auto &record : modify->_add)
modifyXISF.addFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.value.toString().toStdString()});
modifyXISF.addFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
modifyXISF.save(out.absoluteFilePath().toLocal8Bit().toStdString());
modifyXISF.save(out.toLocal8Bit().toStdString());
modifyXISF.close();
std::filesystem::rename(out.filesystemAbsoluteFilePath(), in.filesystemAbsoluteFilePath());
std::filesystem::rename(out.toLocal8Bit().toStdString(), in.toLocal8Bit().toStdString());
return true;
}
catch(std::filesystem::filesystem_error &err)
{
return false;
}
catch(LibXISF::Error &err)
{
_engine->newMessage("Failed to modify file " + _path + " " + err.what(), true);
if(_engine)_engine->newMessage("Failed to modify file " + _path + " " + err.what(), true);
return false;
}
}
@@ -574,6 +746,24 @@ QJSValue File::stats()
return _stats;
}
#ifdef PLATESOLVER
QJSValue File::solve(bool updateHeader)
{
if(_solution.isUndefined() || updateHeader)
_solution = _engine->solveImage(this, updateHeader);
return _solution;
}
QJSValue File::extractStars(bool hfr)
{
if(_stars.isUndefined())
_stars = _engine->extractStars(this, hfr);
return _stars;
}
#endif // PLATESOLVER
ScriptEngineThread::ScriptEngineThread(BatchProcessing *parent) : QObject(parent)
{
_thread = new QThread();
+17 -1
View File
@@ -11,6 +11,7 @@
#include "imageinfo.h"
class BatchProcessing;
class Solver;
namespace Script
{
@@ -28,6 +29,7 @@ class ScriptEngine : public QObject
QString _scriptPath;
QString _outputDir;
QList<QPair<QString, QString>> _paths;
Solver *_solver = nullptr;
public:
explicit ScriptEngine(BatchProcessing *parent = nullptr);
void setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir);
@@ -46,6 +48,14 @@ public:
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;
bool convert(File *file, QString &outpath, const QString &format, const QVariantMap &params, bool async);
#ifdef PLATESOLVER
Q_INVOKABLE void setSolverProfile(int index);
Q_INVOKABLE void setSolverProfile(const QVariantMap &profile);
Q_INVOKABLE QJSValue getSolverProfile() const;
Q_INVOKABLE void setStartingSolution(const QJSValue &solution = QJSValue());
QJSValue solveImage(File *file, bool updateHeader);
QJSValue extractStars(File *file, bool hfr);
#endif // PLATESOLVER
QJSValue newObject();
QJSValue newArray(uint size);
public slots:
@@ -76,7 +86,7 @@ class FITSRecordModify;
class File : public QObject
{
Q_OBJECT
ScriptEngine *_engine;
ScriptEngine *_engine = nullptr;
QString _path;
QString _root;
QFileInfo _info;
@@ -86,6 +96,8 @@ class File : public QObject
void loadFitsKeywords();
bool mkpath(const QString &path) const;
QJSValue _stats;
QJSValue _solution;
QJSValue _stars;
public:
explicit File(const QString &path, ScriptEngine *engine);
explicit File(const QString &path, const QString &root, ScriptEngine *engine);
@@ -109,6 +121,10 @@ public:
Q_INVOKABLE File* convert(const QString &outpath, const QString &format, const QVariantMap &params = QVariantMap());
Q_INVOKABLE File* convertAsync(const QString &outpath, const QString &format, const QVariantMap &params = QVariantMap());
Q_INVOKABLE QJSValue stats();
#ifdef PLATESOLVER
Q_INVOKABLE QJSValue solve(bool updateHeader = false);
Q_INVOKABLE QJSValue extractStars(bool hfr);
#endif // PLATESOLVER
};
class FITSRecordModify : public QObject
-2
View File
@@ -1,5 +1,3 @@
#version 330
uniform sampler2D qt_Texture0;
uniform ivec2 firstRed;
in vec2 qt_TexCoord0;
-2
View File
@@ -1,5 +1,3 @@
#version 330
uniform sampler2D qt_Texture0;
in vec2 qt_Vertex;
in vec2 qt_MultiTexCoord0;
+12 -6
View File
@@ -1,6 +1,5 @@
#version 330
uniform sampler2D qt_Texture0;
uniform sampler3D lut_table;
uniform vec3 mtf_param[3];
uniform vec2 unit_scale;
uniform bool bw;
@@ -22,7 +21,7 @@ vec4 MTF(vec4 x, vec4 low, vec4 mid, vec4 high)
{
x = (x - low) / (high - low);
x = clamp(x, vec4(0.0), vec4(1.0));
return ((mid - 1) * x) / ((2 * mid - 1) * x - mid);
return ((mid - 1.0) * x) / ((2.0 * mid - 1.0) * x - mid);
}
vec3 falsecolor(float color)
@@ -59,7 +58,7 @@ vec4 cubic(float v)
vec4 textureBicubic(sampler2D sampler, vec2 texCoords)
{
vec2 texSize = textureSize(sampler, 0);
vec2 texSize = vec2(textureSize(sampler, 0));
vec2 invTexSize = 1.0 / texSize;
texCoords = texCoords * texSize - 0.5;
@@ -133,7 +132,7 @@ void main(void)
switch(filtering)
{
case 0://nearest
color = texelFetch(qt_Texture0, ivec2(qt_TexCoord0 * textureSize(qt_Texture0, 0)), 0);
color = texelFetch(qt_Texture0, ivec2(qt_TexCoord0 * vec2(textureSize(qt_Texture0, 0))), 0);
break;
default:
case 1://bilinear
@@ -153,7 +152,14 @@ void main(void)
color.rgb = mix(checker(), color.rgb, color.a);
if(srgb)color.rgb = Linear2sRGB(color.rgb);
if(srgb)
{
color.rgb *= 31.0 / 32.0;
color.rgb += 0.5 / 32.0;
vec4 lut = texture(lut_table, vec3(color.rgb));
color.rgb = lut.rgb;
//color.rgb = Linear2sRGB(lut.rgb);
}
if(any(lessThan(qt_TexCoord0, vec2(0.0))) || any(greaterThan(qt_TexCoord0, vec2(1.0))))
color = vec4(0.0, 0.0, 0.0, 1.0);
-2
View File
@@ -1,5 +1,3 @@
#version 330
uniform sampler2D qt_Texture0;
in vec2 qt_Vertex;
in vec2 qt_MultiTexCoord0;
+1 -3
View File
@@ -1,5 +1,3 @@
#version 330
uniform sampler2DArray qt_Texture0;
uniform vec3 mtf_param[3];
uniform bool invert;
@@ -10,7 +8,7 @@ vec4 MTF(vec4 x, vec4 low, vec4 mid, vec4 high)
{
x = (x - low) / (high - low);
x = clamp(x, vec4(0.0), vec4(1.0));
return ((mid - 1) * x) / ((2 * mid - 1) * x - mid);
return ((mid - 1.0) * x) / ((2.0 * mid - 1.0) * x - mid);
}
void main(void)
+3 -5
View File
@@ -1,5 +1,3 @@
#version 330
in vec2 qt_Vertex;
in vec2 qt_MultiTexCoord0;
in ivec3 imageSize_num;
@@ -13,9 +11,9 @@ void main(void)
{
vec2 pos = qt_Vertex * 0.5;
pos.y *= -1.0;
pos = pos * imageSize_num.xy + thumb_size.x;
pos = pos * vec2(imageSize_num.xy) + float(thumb_size.x);
ivec2 off = ivec2(imageSize_num.z % viewport_row.z, imageSize_num.z / viewport_row.z) * thumb_size.yz;
gl_Position = mvp * vec4(pos - offset + off, 0.0, 1.0);
qt_TexCoord0 = vec3(qt_MultiTexCoord0, imageSize_num.z + 0.1);
gl_Position = mvp * vec4(pos - offset + vec2(off), 0.0, 1.0);
qt_TexCoord0 = vec3(qt_MultiTexCoord0, float(imageSize_num.z) + 0.1);
}
+254
View File
@@ -0,0 +1,254 @@
#include "solver.h"
#include <QJsonObject>
#include <QJsonDocument>
#include <fitsio.h>
#include <QStandardPaths>
#include <QSettings>
#include <wcslib/wcshdr.h>
#include <wcslib/wcsutil.h>
#include "rawimage.h"
#include "loadrunable.h"
#include "scriptengine.h"
Solver::Solver(QObject *parent) : QObject(parent)
{
_solver = new StellarSolver(this);
connect(_solver, &StellarSolver::logOutput, this, &Solver::logOutput);
_solver->setProperty("ProcessType", SSolver::SOLVE);
QSettings settings;
setIndexFolder(settings.value("platesolving/indexPath", Solver::getTenmonIndexPath()).toString());
int profileIdx = settings.value("platesolving/profile", 0).toInt();
auto profiles = _solver->getBuiltInProfiles();
_solver->setParameters(profiles[profileIdx]);
connect(_solver, &StellarSolver::finished, this, &Solver::finished);
}
Solver::~Solver()
{
}
void Solver::setIndexFolder(const QString &indexPath)
{
_solver->setIndexFolderPaths(QStringList(indexPath));
}
bool Solver::loadImage(const QString &path)
{
if(path == _path)return true;
_loaded = false;
std::shared_ptr<RawImage> image;
ImageInfoData info;
if(::loadImage(path, info, image, true))
{
return loadImage(image, path);
}
return false;
}
bool Solver::loadImage(std::shared_ptr<RawImage> &image, const QString &path)
{
_rawImage = image;
if(_rawImage->channels() > 1)
_rawImagePlanar = _rawImage->toPlanar();
else
_rawImagePlanar = _rawImage;
switch(_rawImage->type())
{
case RawImage::UINT8:
_stats.dataType = TBYTE;
break;
case RawImage::UINT16:
_stats.dataType = TUSHORT;
break;
case RawImage::UINT32:
_stats.dataType = TUINT;
break;
case RawImage::FLOAT32:
_stats.dataType = TFLOAT;
break;
case RawImage::FLOAT64:
_stats.dataType = TDOUBLE;
break;
default:
_error = tr("Unsupported image data type");
return false;
break;
}
_stats.bytesPerPixel = _rawImage->typeSize(_rawImagePlanar->type());
_stats.channels = _rawImagePlanar->channels();
_stats.width = _rawImagePlanar->width();
_stats.height = _rawImagePlanar->height();
_stats.samples_per_channel = _stats.width * _stats.height;
_solver->clearSearchPosition();
_solver->clearSearchScale();
_loaded = _solver->loadNewImageBuffer(_stats, (const uint8_t*)_rawImagePlanar->data());
_path = path;
return _loaded;
}
bool Solver::solveImage(bool sync)
{
if(_loaded && !_solver->isRunning())
{
_process = SSolver::ProcessType::SOLVE;
_solver->setProperty("ProcessType", _process);
if(sync)return _solver->solve();
else _solver->start();
return true;
}
return false;
}
bool Solver::extractSources(bool hfr, bool sync)
{
if(_loaded && !_solver->isRunning())
{
_process = hfr ? SSolver::ProcessType::EXTRACT_WITH_HFR : SSolver::ProcessType::EXTRACT;
_solver->setProperty("ProcessType", _process);
if(sync)return _solver->extract(hfr);
else _solver->start();
return true;
}
return false;
}
void Solver::abort()
{
_solver->abort();
}
const FITSImage::Solution& Solver::getSolution() const
{
return _solver->getSolution();
}
const QList<FITSImage::Star>& Solver::getStars() const
{
return _solver->getStarList();
}
double Solver::getHFR() const
{
double hfr = 0.0;
auto stars = getStars();
if(stars.empty())return -1.0;
for(auto &star : stars)
{
hfr += star.HFR;
}
return hfr / stars.size();
}
QString Solver::errorMessage() const
{
return _error;
}
bool Solver::updateHeader(QString &error)
{
if(!_solver->solvingDone())
{
error = tr("Solving is not finished");
return false;
}
FITSImage::Solution solution = getSolution();
double rotationDeg = 360.0 - solution.orientation;
if(rotationDeg > 360)rotationDeg -= 360;
double rotationRad = rotationDeg / 180.0 * M_PI;
double cdeltx = (solution.parity == FITSImage::NEGATIVE ? solution.pixscale : -solution.pixscale) / 3600.0;
double cdelty = solution.pixscale / 3600.0;
Script::File file(_path, nullptr);
Script::FITSRecordModify modify;
modify.removeKeyword("RADECSYS");
modify.updateKeyword("CRPIX1", _stats.width / 2.0, QByteArray("x pixel coordinate of the reference point"));
modify.updateKeyword("CRPIX2", _stats.height / 2.0, QByteArray("y pixel coordinate of the reference point"));
modify.updateKeyword("CDELT1", cdeltx, QByteArray("X pixel size (deg)"));
modify.updateKeyword("CDELT2", cdelty, QByteArray("Y pixel size (deg)"));
modify.updateKeyword("CRVAL1", solution.ra, QByteArray("RA of reference pixel (deg)"));
modify.updateKeyword("CRVAL2", solution.dec, QByteArray("DEC of reference pixel (deg)"));
modify.updateKeyword("CD1_1", std::cos(rotationRad) * cdeltx, QByteArray("CD matrix to convert (x,y) to (RA, DEC)"));
modify.updateKeyword("CD1_2",-std::sin(rotationRad) * cdelty, QByteArray("CD matrix to convert (x,y) to (RA, DEC)"));
modify.updateKeyword("CD2_1", std::sin(rotationRad) * cdeltx, QByteArray("CD matrix to convert (x,y) to (RA, DEC)"));
modify.updateKeyword("CD2_2", std::cos(rotationRad) * cdelty, QByteArray("CD matrix to convert (x,y) to (RA, DEC)"));
modify.updateKeyword("CROTA1", rotationDeg, QByteArray("Image twist X axis (deg)"));
modify.updateKeyword("CROTA2", rotationDeg, QByteArray("Image twist Y axis (deg)"));
modify.updateKeyword("CTYPE1", "RA---TAN", QByteArray("first parameter RA, projection TANgential"));
modify.updateKeyword("CTYPE2", "DEC--TAN", QByteArray("first parameter DEC, projection TANgential"));
modify.updateKeyword("RADESYS", "ICRS", QByteArray("International Celestial Reference System"));
modify.updateKeyword("EQUINOX", 2000, QByteArray("Equinox of coordinates"));
bool ret = file.modifyFITSRecords(&modify);
if(!ret)error = tr("Failed to update file header");
return ret;
}
void Solver::setParameters(Parameters::ParametersProfile profile)
{
auto profileParam = _solver->getBuiltInProfiles().at(profile);
profileParam.partition = false;
_solver->setParameters(profileParam);
}
void Solver::setParameters(const Parameters &parameters)
{
auto profile = parameters;
profile.partition = false;
_solver->setParameters(profile);
}
Parameters Solver::getProfile() const
{
return _solver->getCurrentParameters();
}
void Solver::setSearchScale(double fovLow, double fowHigh, SSolver::ScaleUnits units)
{
_solver->setSearchScale(fovLow, fowHigh, units);
}
void Solver::setSearchPosition(double ra, double dec)
{
_solver->setSearchPositionRaDec(ra, dec);
}
void Solver::clearStartingPositionAndScale()
{
_solver->clearSearchPosition();
_solver->clearSearchScale();
}
QStringList Solver::getIndexPaths()
{
QStringList paths = StellarSolver::getDefaultIndexFolderPaths();
paths.prepend(getTenmonIndexPath());
return paths;
}
QString Solver::getTenmonIndexPath()
{
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/astrometry";
}
void Solver::finished()
{
switch(_process)
{
case SSolver::ProcessType::SOLVE:
emit solvingDone();
break;
case SSolver::ProcessType::EXTRACT_WITH_HFR:
case SSolver::ProcessType::EXTRACT:
emit extractionDone();
break;
}
}
+52
View File
@@ -0,0 +1,52 @@
#ifndef SOLVER_H
#define SOLVER_H
#include <stellarsolver.h>
class RawImage;
class Solver : public QObject
{
Q_OBJECT
StellarSolver *_solver;
FITSImage::Statistic _stats;
SSolver::ProcessType _process = SSolver::SOLVE;
bool _loaded = false;
QString _path;
QString _error;
std::shared_ptr<RawImage> _rawImage;
std::shared_ptr<RawImage> _rawImagePlanar;
public:
explicit Solver(QObject *parent = nullptr);
~Solver();
void setIndexFolder(const QString &indexPath);
bool loadImage(const QString &path);
bool loadImage(std::shared_ptr<RawImage> &image, const QString &path);
bool solveImage(bool sync = false);
bool extractSources(bool hfr, bool sync = false);
void abort();
const FITSImage::Solution& getSolution() const;
const QList<FITSImage::Star>& getStars() const;
double getHFR() const;
QString errorMessage() const;
bool updateHeader(QString &error);
void setParameters(SSolver::Parameters::ParametersProfile profile);
void setParameters(const SSolver::Parameters &parameters);
SSolver::Parameters getProfile() const;
void setSearchScale(double fovLow, double fowHigh, ScaleUnits units);
void setSearchPosition(double ra, double dec);
void clearStartingPositionAndScale();
static QStringList getIndexPaths();
static QString getTenmonIndexPath();
public slots:
void finished();
signals:
void solvingDone();
void extractionDone();
void logOutput(const QString &log);
};
#endif // SOLVER_H
+34 -2
View File
@@ -57,6 +57,38 @@
</screenshots>
<content_rating type="oars-1.1"/>
<releases>
<release version="20250126" date="2025-01-26">
<description>
<ul>
<li>Support for really big images +50000px</li>
<li>Fix handling of MAX_PATH on Windows</li>
<li>Add setting solver profile in scripts</li>
</ul>
</description>
</release>
<release version="20241116" date="2024-11-16">
<description>
<ul>
<li>Extending support of data formats</li>
</ul>
</description>
</release>
<release version="20241002" date="2024-10-02">
<description>
<ul>
<li>Plate solving</li>
<li>Open marked files as directory</li>
<li>Linux ARM port</li>
<li>Improved ICC color profile handling</li>
<li>Add *.fz and *.fts as FITS extension</li>
</ul>
</description>
</release>
<release version="20240816" date="2024-08-16">
<description>
Fix saving image
</description>
</release>
<release version="20240616" date="2024-06-16">
<description>
<ul>
@@ -65,7 +97,7 @@
</ul>
</description>
</release>
<release version="20240201" date="2024-02-01">
<release version="20240201" date="2024-02-01">
<description>
<ul>
<li>Smooth thumbnails</li>
@@ -73,7 +105,7 @@
<li>Bugfixes</li>
</ul>
</description>
</release>
</release>
<release version="20240108" date="2024-01-08">
<description>
<ul>
+9
View File
@@ -2,6 +2,7 @@
#include <QVBoxLayout>
#include <QDebug>
#include <QToolButton>
#include <QSettings>
#include "imageringlist.h"
const float BLACK_POINT_SIGMA = -2.8f;
@@ -91,6 +92,14 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
m_autoStretchOnLoad = addAction(QIcon(":/nuke_a.png"), tr("Apply auto stretch on load"));
m_autoStretchOnLoad->setCheckable(true);
QSettings settings;
m_autoStretchOnLoad->setChecked(settings.value("stretchtoolbar/autostretch", false).toBool());
}
StretchToolbar::~StretchToolbar()
{
QSettings settings;
settings.setValue("stretchtoolbar/autostretch", m_autoStretchOnLoad->isChecked());
}
void StretchToolbar::stretchImage(Image *img)
+1
View File
@@ -27,6 +27,7 @@ class StretchToolbar : public QToolBar
MTFParam m_mtfParam;
public:
explicit StretchToolbar(QWidget *parent = nullptr);
~StretchToolbar();
public slots:
void stretchImage(Image *img);
void resetMTF();
Binary file not shown.
+596 -188
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+598 -190
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+596 -188
View File
File diff suppressed because it is too large Load Diff