Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4afa940886 | |||
| d1344d2dc8 | |||
| 24eea573e6 | |||
| 8f7f527732 | |||
| 3635ac00cb | |||
| eba9110933 | |||
| 464207beb1 | |||
| 4aeff61c44 | |||
| 790c836bbd | |||
| 62616898ed | |||
| e216af6a6d | |||
| e0d6f417a0 | |||
| 3c5fef988e | |||
| 6c42315f87 | |||
| 06b3dbc1bb | |||
| 258553a6bb | |||
| 9c40ce2daa | |||
| 9f4c4c8bdc | |||
| da1aa4c6fc | |||
| a43f12565d | |||
| 32973c54ce | |||
| dccb2e88da | |||
| c8898387fe | |||
| dfe31b6350 | |||
| 553e72a5ce | |||
| 12901c9a47 | |||
| da79197376 | |||
| 30960033c5 | |||
| efd3ff35f3 | |||
| 52bcb10da1 | |||
| 9adfbde512 | |||
| e38de510a0 | |||
| 5e18c591f7 | |||
| d6e257e201 | |||
| 2c7a7d473f | |||
| 87d7bd2d9f | |||
| 79dd7d91eb | |||
| 21b4e0934c | |||
| 0239aba165 | |||
| 1b08242433 | |||
| a56e8a2c27 | |||
| bf360c1ae1 | |||
| 1ec3a6cffd | |||
| 100f47746c | |||
| 02bac0c850 | |||
| bc29dc7d34 | |||
| ff5053b626 | |||
| 511802bdbd | |||
| dd16a02045 | |||
| 7ed38cf6d7 | |||
| 8c6b451564 | |||
| d288810d5d | |||
| fb66e82428 | |||
| 71486efeef | |||
| 8213f6213f | |||
| f8c9fec77e | |||
| af4be850cb | |||
| ca1a13ed9d | |||
| 1873da6c49 | |||
| 92345f82ca | |||
| 3c8f49e932 | |||
| 37dd97e361 | |||
| da31187aa3 | |||
| 4801338160 | |||
| fb9d026ff5 | |||
| c2810faf8f | |||
| d3d302fd38 | |||
| a0497c7d19 | |||
| 8818e25eda | |||
| c3baa18087 | |||
| 66f0c05a48 | |||
| 461ffea874 | |||
| 7535ad87e7 | |||
| 273aef1594 | |||
| 9519c9830c | |||
| 342e5cc5db | |||
| ae84cbdfe0 | |||
| 933fd4a2a0 | |||
| c3588e1c36 | |||
| 174134a9ee | |||
| bbc13ec8a5 | |||
| 9f7e2ab6b4 | |||
| 4fe56acbd9 | |||
| f35db9d1af | |||
| 81d138f799 | |||
| ae07d4793b | |||
| dc2a781d3b | |||
| 90035f44ed | |||
| 53c9a58125 | |||
| 3f7e3689e8 | |||
| af9187737f | |||
| 4e952873e3 | |||
| fb24800050 | |||
| ea0dcc226a | |||
| 6a7b677b95 | |||
| 0cee4c9c53 | |||
| d5f2351905 | |||
| 18732a8cbf | |||
| 8c9c1d8d06 | |||
| e5f425ff8d | |||
| 428f9c360a | |||
| a8a1509db7 | |||
| 6539c78c57 | |||
| 0e0d29320e | |||
| 1efe8e6974 | |||
| dae10182d1 | |||
| ed5fc9c1c2 | |||
| cd6a64a98b | |||
| 67355a82b7 | |||
| 8fc2078a3a | |||
| da9b389409 | |||
| 7818b8d3e9 | |||
| 11294bfcb0 | |||
| faecb385aa | |||
| e5be04926b | |||
| eaf2c7094b | |||
| aef41f5f6b | |||
| 2134f13b06 | |||
| 0e9c980325 | |||
| b9bf6bf183 |
@@ -17,33 +17,38 @@ if(SANITIZE_ADDRESS_LEAK)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak")
|
||||
endif(SANITIZE_ADDRESS_LEAK)
|
||||
|
||||
find_package(Qt5 COMPONENTS Widgets Sql OpenGL REQUIRED)
|
||||
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml REQUIRED)
|
||||
find_library(GSL_LIB gsl REQUIRED)
|
||||
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)
|
||||
|
||||
set(TENMON_SRC
|
||||
about.cpp about.h
|
||||
batchprocessing.cpp batchprocessing.h batchprocessing.ui
|
||||
database.cpp database.h
|
||||
databaseview.cpp databaseview.h
|
||||
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
|
||||
markedfiles.cpp markedfiles.h
|
||||
rawimage.cpp rawimage.h
|
||||
rawimage_sse.cpp
|
||||
scriptengine.cpp scriptengine.h
|
||||
settingsdialog.cpp settingsdialog.h
|
||||
starfit.cpp starfit.h
|
||||
statusbar.cpp statusbar.h
|
||||
@@ -51,49 +56,65 @@ set(TENMON_SRC
|
||||
stretchtoolbar.cpp stretchtoolbar.h
|
||||
)
|
||||
|
||||
option(COLOR_MANAGMENT "Enable sRGB framebuffer support for gamma correct images and color profiles support" ON)
|
||||
if(${Qt5Core_VERSION_STRING} VERSION_LESS "5.14")
|
||||
set(COLOR_MANAGMENT OFF)
|
||||
endif(${Qt5Core_VERSION_STRING} VERSION_LESS "5.14")
|
||||
|
||||
if(COLOR_MANAGMENT)
|
||||
add_compile_definitions("COLOR_MANAGMENT")
|
||||
endif(COLOR_MANAGMENT)
|
||||
|
||||
qt5_add_resources(TENMON_SRC resources.qrc)
|
||||
qt_add_resources(TENMON_SRC resources/resources.qrc)
|
||||
qt_add_resources(TENMON_SRC shaders/shaders.qrc)
|
||||
qt_add_resources(TENMON_SRC scripts/scripts.qrc)
|
||||
if(WIN32)
|
||||
list(APPEND TENMON_SRC icon.rc)
|
||||
list(APPEND TENMON_SRC resources/icon.rc)
|
||||
set(tenmon_ICON "")
|
||||
elseif(APPLE)
|
||||
set(tenmon_ICON ${CMAKE_CURRENT_SOURCE_DIR}/tenmon.icns)
|
||||
set(tenmon_ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/tenmon.icns)
|
||||
find_package(Qt6 COMPONENTS DBus REQUIRED)
|
||||
set_source_files_properties(${tenmon_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
|
||||
else()
|
||||
set(tenmon_ICON "")
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_search_module(GIO REQUIRED gio-2.0)
|
||||
find_package(Qt6 COMPONENTS DBus REQUIRED)
|
||||
endif()
|
||||
|
||||
add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC})
|
||||
qt_add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC})
|
||||
|
||||
find_path(FITS_INCLUDE fitsio2.h PATH_SUFFIXES cfitsio REQUIRED)
|
||||
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 Qt5::Widgets Qt5::Sql ${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 "-framework CoreFoundation")
|
||||
else()
|
||||
target_link_libraries(tenmon ${GIO_LDFLAGS})
|
||||
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
|
||||
elseif(UNIX)
|
||||
target_link_libraries(tenmon PRIVATE Qt6::DBus)
|
||||
endif(APPLE)
|
||||
|
||||
if(LIBRAW_STATIC)
|
||||
add_compile_definitions("LIBRAW_NODLL")
|
||||
target_link_libraries(tenmon jasper)
|
||||
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)
|
||||
@@ -102,8 +123,8 @@ if(UNIX AND NOT APPLE)
|
||||
install(SCRIPT install.cmake)
|
||||
else()
|
||||
install(FILES space.nouspiro.tenmon.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
|
||||
install(FILES space.nouspiro.tenmon.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps")
|
||||
install(FILES space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
|
||||
install(FILES resources/space.nouspiro.tenmon.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps")
|
||||
install(FILES resources/space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
|
||||
endif()
|
||||
install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo")
|
||||
endif(UNIX AND NOT APPLE)
|
||||
|
||||
@@ -2,23 +2,30 @@ FITS/XISF image viewer with multithreaded image loading
|
||||
|
||||
To get all dependencies install these packages
|
||||
|
||||
sudo apt install qtbase5-dev libraw-dev libexif-dev libcfitsio-dev libgsl-dev wcslib-dev libopencv-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 opencv-devel gsl-devel exif-devel libraw-devel wcslib-devel libqt5-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 qt5 libraw cfitsio libexif libgsl wcslib opencv
|
||||
homebrew install qt6 libraw cfitsio libexif libgsl wcslib
|
||||
|
||||
You may need to set CMAKE_PREFIX_PATH for Qt5 and OpenCV so CMake can find them.
|
||||
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.
|
||||
|
||||
@@ -46,3 +46,10 @@ HelpDialog::HelpDialog(QWidget *parent) : QDialog(parent)
|
||||
|
||||
layout->addWidget(helpText);
|
||||
}
|
||||
|
||||
QString getVersion()
|
||||
{
|
||||
QString version = GITVERSION;
|
||||
version.truncate(8);
|
||||
return version;
|
||||
}
|
||||
|
||||
@@ -17,4 +17,6 @@ public:
|
||||
HelpDialog(QWidget *parent = nullptr);
|
||||
};
|
||||
|
||||
QString getVersion();
|
||||
|
||||
#endif // ABOUT_H
|
||||
|
||||
@@ -14,7 +14,7 @@ img { margin: 5px; }
|
||||
<li>FITS 8, 16, 32 bit integer and 32, 64 bit float</li>
|
||||
<li>XISF 8, 16, 32 bit integer and 32, 64 bit float</li>
|
||||
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM and SVG images</li>
|
||||
<li>CR2, NEF, DNG raw images</li>
|
||||
<li>CR2, CR3, NEF, DNG raw images</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
@@ -115,6 +115,151 @@ Pressing Enter or clicking on <i>Filter</i> button will filter out database reco
|
||||
<br><img src=":/about/filter.png"><br>
|
||||
This example filters for files where: "Bias" is in the file name, the OBJECT property is "M_42" (where the underscore can be any single character), and the DATE property begins with "2022".
|
||||
</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>
|
||||
|
||||
This module allow to write scripts in JavaScript that process image files. Batch Processing window consist from three main parts. On top is list of input files and directories.
|
||||
You can add directories or individual files to this list. Directories are scanned recursively to find all files even non image files. This list of files is then passed to script in array named <b>files</b>.
|
||||
In script you can then iterate through files like this.
|
||||
<pre>for(file of files)
|
||||
{
|
||||
if(file.suffix() == "fits")
|
||||
{
|
||||
core.log(file.fileName());
|
||||
file.convert(file.relativeFilePath(), "XISF");
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
<p>Bellow this list is output directory. All relative paths passed as arguments into methods like copy(), move() or convert() will be relative to this output directory. If you pass absolute path to methods then
|
||||
this output directory is ignored.</p>
|
||||
|
||||
<p>Bellow that is list of scripts. These scripts are located in application data directory. Select script which you want to run by clicking on it. Clicking on <i>Open scripts</i> will open directory with these scripts where you create new and edit them.
|
||||
Location of this directory is on Windows: "C:/Users/<USER>/AppData/Roaming/nou/Tenmon" Linux: "~/.local/share/nou/Tenmon/scripts" MacOS: "~/Library/Application Support/nou/Tenmon/scripts"</p>
|
||||
|
||||
<p>Next is Log windows that contain any messages that come from scripts. Mainly calls to <code>core.log()</code> At bottom there buttons that can start or stop execution of selected scripts.</p>
|
||||
|
||||
<h4>core</h4>
|
||||
There is global object called <b>core</b> that have these methods.
|
||||
<ul>
|
||||
<li><b>log(message)</b> print message to log window.</li>
|
||||
<li><b>mark(file)</b> mark file same way as in GUI. Takes object of type <i>File</i> as argument.</li>
|
||||
<li><b>unmark(file)</b> unmark file same was as in GUI. Takes object of type <i>File</i> as argument.</li>
|
||||
<li><b>isMarked(file)</b> check if file was marked. Takes object of type <i>File</i> as argument.</li>
|
||||
<li><b>setMaxThread(maxthread)</b> set maximum number of concurrent thread when doing asynchronous task.</li>
|
||||
<li><b>sync()</b> wait until all asynchronous tasks are done.</li>
|
||||
<li><b>getString(label = "", text = "")</b> show dialog box to get string value from user. String value passed in first argument is used as description label. Second argument text is default value in text box.
|
||||
Both parameters are optional so calling just <i>getString()</i> is valid. When cancel is pressed it return Undefined.</li>
|
||||
<li><b>getInt(label = "", value = 0)</b> show dialog box with input box to retrieve integer value. String value passed in first argument is used as description label.
|
||||
Second parameter is default value in input box. Both parameters are optional. When cancel is pressed it return Undefined.</li>
|
||||
<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>
|
||||
In <b>files</b> array there are instances of type <b>File</b> objects that have these methods.
|
||||
<ul>
|
||||
<li><b>fileName()</b> returns the name of the file, excluding the path.</li>
|
||||
<li><b>absoluteFilePath()</b> returns an absolute path including the file name.</li>
|
||||
<li><b>absolutePath()</b> returns an absolute path without the file name</li>
|
||||
<li><b>relativeFilePath()</b> return relative path including file name relative to directory that was in list of directories to be scanned. For example you add C:/images as input directory. In this directory there
|
||||
is file <i>C:/images/lights/red/M42_001.fits</i> then this method will return <i>lights/red/M42_001.fits</i></li>
|
||||
<li><b>relativePath()</b> return same path as previous method just without file name. <i>lights/red</i></li>
|
||||
<li><b>baseName()</b> return file name up to the first dot. For example for <i>some.file.name.fits</i> it will return <i>some</i></li>
|
||||
<li><b>completeBaseName()</b> return file name up to the last dot. For example for <i>some.file.name.fits</i> it will return <i>some.file.name</i></li>
|
||||
<li><b>suffix()</b> return string after last dot in file name. For example <i>fits</i></li>
|
||||
<li><b>size()</b> return size of file in bytes.</li>
|
||||
<li><b>fitsKeywords()</b> return array of strings with every keyword that is in header. <i>SIMPLE,BITPIX,NAXIS,NAXIS1,NAXIS2,EXTEND,COMMENT</i></li>
|
||||
<li><b>fitsValue(key)</b> return value for keyword. In case that there is multiple occurrences it return last one.</li>
|
||||
<li><b>fitsValues(key)</b> return array of values for keyword.</li>
|
||||
<li><b>fitsRecords()</b> return array of objects with properties <b>key, value</b> and <b>comment</b> </li>
|
||||
<li><b>modifyFITSRecords(FITSRecordModify)</b> modify FITS header by adding, removing or updating FITS record. Return true on success. Refer to <i>FITSRecordModify</i></li>
|
||||
<li><b>isMarked()</b> return true if file is marked.</li>
|
||||
<li><b>copy(newpath)</b> copy file to new location. It return instance of new <i>File<i> object that represent this copied file. This path can be relative or absolute. In case that <i>newpath</i> parameter is relative
|
||||
path then it "Output directory" from GUI windows is used as base directory. Parameter <i>newpath</i> can absolute path. File is then copied to this path. In case that copy fail it return null.</li>
|
||||
<li><b>move(newpath)</b> move file to new location. It return false if move failed. This can happend if destination is not writable but also if destination file already exist. This functions does not overwrite existing file.
|
||||
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", "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.
|
||||
It return new instance of <i>File</i> that point to converted file.
|
||||
<pre>file.convert("converted_file.xisf", "xisf", {"compressionType": "zstd+sh", "compressionLevel": 70});
|
||||
file.convert("converted_file.fits", "fits", {"compressionType": "rice"});
|
||||
file.convert("converted_file.jpg", "png");</pre>
|
||||
</li>
|
||||
<li><b>convertAsync(outpath, format, params)</b> same as previous method but it does conversion in separated thread asynchronously and in parallel. Before calling any method on object returned by this method you must call
|
||||
<code>core.sync();</code> to ensure that conversion is done and destination file exists.
|
||||
<pre>let compression = {"compressionType": "zstd+sh"};
|
||||
let convertedFiles = [];
|
||||
for(file in files)
|
||||
{
|
||||
if(file.suffix() == "fits")
|
||||
convertedFiles.push(file.convertAsync("xisf/" + file.fileName(), "XISF", compression));
|
||||
}
|
||||
core.sync(); // ensure that files exist
|
||||
for(file of convertedFiles)// now we can iterate over the files
|
||||
{
|
||||
core.log(file.fileName() + " " + file.size()); // let print compressed file sizes
|
||||
}</pre></li>
|
||||
<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>
|
||||
This class is used to define modify operation FITS header in FITS and XISF files. It can remove update and add records. Order of operation is also remove then update and last add.
|
||||
The keyword names may be up to 8 characters long and can only contain uppercase letters, the digits 0-9, the hyphen, and the underscore character.
|
||||
<pre>let modify = new FITSRecordModify();
|
||||
modify.updateKeyword("OBJECT", "M42");
|
||||
modify.updateKeyword("MYTILE", "PART1", "adding custom keyword so WBPP can group it");
|
||||
modify.removeKeyword("OBJECT");
|
||||
// doesn't matter that it is specified as last. This will first remove
|
||||
// existing OBJECT record and then add again OBJECT=M42
|
||||
for(file in files)
|
||||
{
|
||||
file.modifyFITSRecords(modify);
|
||||
}</pre>
|
||||
|
||||
<ul>
|
||||
<li><b>new FITSRecordModify()</b> create new instance of object.</li>
|
||||
<li><b>removeKeyword(key);</b> specify removing of record with <i>key</i> as keyword.</li>
|
||||
<li><b>updateKeyword(key, value, comment = "")</b> specify updating existing keyword with value and comment. Comment is optional parameter. If record with keyword doesn't exist then it will add new one.
|
||||
Unless you want to have multiple records with same keyword (for example HISTORY) always use this method and not addKeyword.</li>
|
||||
<li><b>addKeyword(key, value, comment = "")</b> specify adding new keyword</li>
|
||||
</ul>
|
||||
|
||||
<p><small>PS: Kanji in icon means astronomy in Japanese</small></p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -14,7 +14,7 @@ img { margin: 5px; }
|
||||
<li>FITS 8, 16 bit entier et 32 bit point flottant</li>
|
||||
<li>XISF 8, 16 bit entier et 32 bit point flottant</li>
|
||||
<li>images JPEG, PNG, BMP, GIF, PBM, PGM, PPM et SVG</li>
|
||||
<li>images RAW CR2, NEF, DNG</li>
|
||||
<li>images RAW CR2, CR3, NEF, DNG</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
@@ -101,6 +101,124 @@ En appuyant sur la touche Enter ou en cliquant sur le bouton <i>Filtre</i>, les
|
||||
<br><img src=":/about/filter.png"><br>
|
||||
Cet exemple filtre les fichiers où : "Bias" figure dans le nom de fichier, la propriété OBJECT est "M_42" (où le trait de soulignement peut être n'importe quel caractère) et la propriété DATE commence par "2022".
|
||||
</p>
|
||||
|
||||
<h3>Traitement par lot</h3>
|
||||
|
||||
Ce module permet d'écrire des scripts en JavaScript qui traitent des fichiers images. La fenêtre de traitement par lots se compose de trois parties principales. En haut se trouve la liste des fichiers et répertoires d'entrée.
|
||||
Vous pouvez ajouter des répertoires ou des fichiers individuels à cette liste. Les répertoires sont analysés de manière récursive pour trouver tous les fichiers, même les fichiers non image. Cette liste de fichiers est ensuite transmise au script dans un tableau nommé <b>files</b>.
|
||||
Dans le script, vous pouvez ensuite parcourir les fichiers comme ici.
|
||||
<pre>for(file of files)
|
||||
{
|
||||
if(file.suffix() == "fits")
|
||||
{
|
||||
core.log(file.fileName());
|
||||
file.convert(file.relativeFilePath(), "XISF");
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h4>core</h4>
|
||||
Il existe un objet global appelé <b>core</b> qui possède ces méthodes.
|
||||
<ul>
|
||||
<li><b>log(message)</b> afficher le message dans la fenêtre du journal.</li>
|
||||
<li><b>mark(file)</b> marquer le fichier de la même manière que dans l'interface graphique. Prend un objet de type <i>File</i> comme argument.</li>
|
||||
<li><b>unmark(file)</b> décoche le fichier de la même manière que dans l'interface graphique. Prend un objet de type <i>File</i> comme argument.</li>
|
||||
<li><b>isMarked(file)</b> vérifie si le fichier a été marqué. Prend un objet de type <i>File</i> comme argument.</li>
|
||||
<li><b>setMaxThread(maxthread)</b> définir le nombre maximal de threads simultanés lors de l'exécution d'une tâche asynchrone.</li>
|
||||
<li><b>sync()</b> attendre que toutes les tâches asynchrones soient terminées.</li>
|
||||
<li><b>getString(label = "", text = "")</b> affiche la boîte de dialogue pour obtenir un text de l'utilisateur. La valeur text passée dans le premier argument est utilisée comme label de description. Le texte du deuxième argument est la valeur par défaut dans la zone de texte.
|
||||
Les deux paramètres sont facultatifs, donc l'appel à <i>getString()</i> est valide. Lorsque vous appuyez sur Annuler, il renvoie Undefined</li>
|
||||
<li><b>getInt(label = "", value = 0)</b> affiche une boîte de dialogue avec une zone de saisie pour récupérer une valeur entière. 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 deux paramètres sont facultatifs. Lorsque vous appuyez sur Annuler, il renvoie Undefined.</li>
|
||||
<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>
|
||||
Dans le tableau <b>files</b>, il y a des instances d'objets de type <b>File</b> qui ont ces méthodes.
|
||||
<ul>
|
||||
<li><b>fileName()</b> renvoie le nom du fichier, à l'exclusion du chemin.</li>
|
||||
<li><b>absoluteFilePath()</b> renvoie un chemin absolu incluant le nom du fichier.</li>
|
||||
<li><b>absolutePath()</b> renvoie un chemin absolu sans le nom du fichier</li>
|
||||
<li><b>relativeFilePath()</b> renvoie le chemin relatif incluant le nom du fichier par rapport au répertoire qui était dans la liste des répertoires à analyser. Par exemple, vous ajoutez C:/images comme répertoire d'entrée. Dans ce répertoire, il y a
|
||||
le fichier <i>C:/images/lights/red/M42_001.fits</i>, alors cette méthode renverra <i>lights/red/M42_001.fits</i></li>
|
||||
<li><b>relativePath()</b> renvoie le même chemin que la méthode précédente, mais sans le nom de fichier. <i>lights/red</i></li>
|
||||
<li><b>baseName()</b> renvoie le nom du fichier jusqu'au premier point. Par exemple, pour <i>some.file.name.fits</i>, il renverra <i>some</i></li>
|
||||
<li><b>completeBaseName()</b> renvoie le nom du fichier jusqu'au dernier point. Par exemple, pour <i>some.file.name.fits</i>, il renverra <i>some.file.name</i></li>
|
||||
<li><b>suffix()</b> renvoie la chaîne après le dernier point du nom de fichier. Par exemple <i>fits</i></li>
|
||||
<li><b>size()</b> renvoie la taille du fichier en octets.</li>
|
||||
<li><b>fitsKeywords()</b> renvoie un tableau de chaînes avec chaque mot-clé présent dans l'en-tête. <i>SIMPLE,BITPIX,NAXIS,NAXIS1,NAXIS2,EXTEND,COMMENT</i></li>
|
||||
<li><b>fitsValue(key)</b> renvoie la valeur pour le mot-clé. En cas d'occurrences multiples, la dernière est renvoyée.</li>
|
||||
<li><b>fitsValues(key)</b> renvoie un tableau de valeurs pour le mot clé.</li>
|
||||
<li><b>fitsRecords()</b> renvoie un tableau d'objets avec des propriétés <b>key, value</b> et <b>comment</b> </li>
|
||||
<li><b>modifyFITSRecords(FITSRecordModify)</b> modifier l'en-tête FITS en ajoutant, supprimant ou mettant à jour l'enregistrement FITS. Renvoie true en cas de succès. Reportez-vous à <i>FITSRecordModify</i></li>
|
||||
<li><b>isMarked()</b> renvoie true si le fichier est marqué.</li>
|
||||
<li><b>copy(newpath)</b> Copie le fichier vers un nouvel emplacement. Il renvoie une instance du nouvel objet <i>File<i> qui représente ce fichier copié. 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 copié vers ce chemin. En cas d'échec de la copie, il renvoie null.</li>
|
||||
<li><b>move(newpath)</b> déplacer le fichier vers un nouvel emplacement. Il renvoie false si le déplacement a échoué. Cela peut se produire si la destination n'est pas accessible en écriture mais aussi si le fichier de destination existe déjà. Cette fonction n'écrase pas le fichier existant.
|
||||
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", "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é.
|
||||
Il renvoie une nouvelle instance de <i>File</i> qui pointe vers le fichier converti.
|
||||
<pre>file.convert("converted_file.xisf", "xisf", {"compressionType": "zstd+sh", "compressionLevel": 70});
|
||||
file.convert("converted_file.fits", "fits", {"compressionType": "rice"});
|
||||
file.convert("converted_file.jpg", "png");</pre>
|
||||
</li>
|
||||
<li><b>convertAsync(outpath, format, params)</b> même méthode que la précédente, mais effectue la conversion dans un thread séparé de manière asynchrone et en parallèle. Avant d'appeler une méthode sur un objet renvoyé par cette méthode, vous devez appeler
|
||||
<code>core.sync();</code> pour s'assurer que la conversion est effectuée et que le fichier de destination existe.
|
||||
<pre>let compression = {"compressionType": "zstd+sh"};
|
||||
let convertedFiles = [];
|
||||
for(file of files)
|
||||
{
|
||||
if(file.suffix() == "fits")
|
||||
convertedFiles.push(file.convertAsync("xisf/" + file.fileName(), "XISF", compression));
|
||||
}
|
||||
core.sync(); // ensure that files exist
|
||||
for(file of convertedFiles)// now we can iterate over the files
|
||||
{
|
||||
core.log(file.fileName() + " " + file.size()); // let print compressed file sizes
|
||||
}</pre></li>
|
||||
<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>
|
||||
Cette classe est utilisée pour définir l'en-tête FITS des opérations de modification dans les fichiers FITS et XISF. Elle peut supprimer, mettre à jour et ajouter des enregistrements. L'ordre des opérations est également le suivant : suppression, puis mise à jour et enfin ajout.
|
||||
Les noms des mots-clés peuvent comporter jusqu'à 8 caractères et ne peuvent contenir que des lettres majuscules, les chiffres de 0 à 9, le trait d'union et le caractère de soulignement.
|
||||
<pre>let modify = new FITSRecordModify();
|
||||
modify.updateKeyword("OBJECT", "M42");
|
||||
modify.updateKeyword("MYTILE", "PART1", "adding custom keyword so WBPP can group it");
|
||||
modify.removeKeyword("OBJECT");
|
||||
// Peu importe qu'il soit spécifié comme dernier. Cela supprimera d'abord
|
||||
// l'enregistrement OBJECT existant, puis ajoutera à nouveau OBJECT=M42
|
||||
for(file in files)
|
||||
{
|
||||
file.modifyFITSRecords(modify);
|
||||
}</pre>
|
||||
|
||||
<ul>
|
||||
<li><b>new FITSRecordModify()</b> créer une nouvelle instance de l'objet.</li>
|
||||
<li><b>removeKeyword(key);</b> spécifier la suppression de l'enregistrement avec <i>key</i> comme mot-clé.</li>
|
||||
<li><b>updateKeyword(key, value, comment = "")</b> spécifiez la mise à jour du mot-clé existant avec la valeur et le commentaire. Le commentaire est un paramètre facultatif. Si l'enregistrement avec le mot-clé n'existe pas, il en ajoutera un nouveau.
|
||||
À moins que vous ne souhaitiez avoir plusieurs enregistrements avec le même mot-clé (par exemple HISTORY), utilisez toujours cette méthode et non addKeyword.</li>
|
||||
<li><b>addKeyword(key, value, comment = "")</b> spécifier l'ajout d'un nouveau mot-clé</li>
|
||||
</ul>
|
||||
|
||||
<p><small>PS: Le Kanji de icône (tenmon) signifie astronomie en japonais</small></p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -13,7 +13,7 @@ p { padding:0px; margin:5px 5px 10px 5px; }
|
||||
<li>FITS 8, 16, 32 bitové celočíselné 32 a 64 bitové s plávajúcou čiarkou</li>
|
||||
<li>XISF 8, 16, 32 bitové celočíselné 32 a 64 bitové s plávajúcou čiarkou</li>
|
||||
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM a SVG obrázky</li>
|
||||
<li>CR2, NEF, DNG raw obrázky</li>
|
||||
<li>CR2, CR3, NEF, DNG raw obrázky</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
@@ -78,9 +78,138 @@ Pre indexovanie nových súborov je treba znova pustiť indexáciu.</p>
|
||||
kde sú jednotlivé stĺpcoch zobrazené vybrané vlastnosti. V spodnej časti panelu je tlačidlo ktoré zobrazí dialóg na výber zobrazovaných
|
||||
sĺpcov. Nasledujú tri výberové a textové polia. Tieto slúžia na vyhľadávanie v databáze. Výberové pole určuje stĺpec v ktorom sa
|
||||
má vyhľadávať a do textového poľa sa zadáva hodnota na vyhľadanie.
|
||||
|
||||
<p>Zastupné znaky:
|
||||
<ul>
|
||||
<li><b>%</b> (percent) je zastupný znak reprezentujúci žiadný alebo hocikoľko znakov.</li>
|
||||
<li><b>_</b> (underscore) je zastupný znak nahrádzajúci presne jeden znak.</i>
|
||||
<li>Bez zástupných znako sa hľadá presná zhoda.</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<br><img src=":/about/filter.png"><br>
|
||||
V nasledovnom príklade sa vyhľadajú súbory ktoré majú v mene súboru "Bias", OBJECT je M_42 a DATE začína reťazcom 2022. Znak % sa berie ako
|
||||
zástupný znak za hocijaký reťazec znakov aj žiadny. Znak _ je tiež zástupný znak zastupujúci práve jeden znak.
|
||||
Bez použitia zástupných znakov sa vyhľadá iba presný výskyt.</p>
|
||||
|
||||
<h3>Hromadné spracovanie</h3>
|
||||
|
||||
Tento modul umožnuje písanie skriptov v JavaScripte ktoré spracujú súbory obrázkov. Okno Hromadného spracovanie pozostáva z troch častí. Navrchu je zoznam vstupných súborov a adresárov.
|
||||
Do zoznamu môžete pridať adresáre alebo jednotlivé súbory. Adresáre sú rekurzívne prehľadané na všetky súbory. Zoznam súborov je potom predaný do skriptu ako pole nazvané <b>files</b>.
|
||||
V skripte potom cez toto pole iteruje nasledovne.
|
||||
<pre>for(file in files)
|
||||
{
|
||||
if(file.suffix() == "fits")
|
||||
{
|
||||
core.log(file.fileName());
|
||||
file.convert(file.relativeFilePath(), "XISF");
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h4>core</h4>
|
||||
V skripte je dostupný globálny objekt nazvaný <b>core</b> ktorý má nasledovné metódy.
|
||||
<ul>
|
||||
<li><b>log(message)</b> vypíše message do okna záznamu.</li>
|
||||
<li><b>mark(file)</b> označí súbor rovnako ako cez GUI. Parameter je objekt typu <i>File</i>.</li>
|
||||
<li><b>unmark(file)</b> odznačí súbor rovnako ako cez GUI. Parameter je objekt typu <i>File</i>.</li>
|
||||
<li><b>isMarked(file)</b> overenie či je súbor označený. Parameter je objekt typu <i>File</i>.</li>
|
||||
<li><b>setMaxThread(maxthread)</b> nastavý maximálny počet paralelných vlákien pri vykonávaní asynchrónnych úloh.</li>
|
||||
<li><b>sync()</b> počká kým sa dokončia všetky asynchrónne úlohy.</li>
|
||||
<li><b>getString(label = "", text = "")</b> ukáže dialóg pre získanie textovej hodnoty od používateľa. Prvý parameter je textový popis. Druhý parameter je východzia hodnota. Obydva parametre sú voliteľné takže aj volanie <i>getString()</i> je valdiné.
|
||||
Metoda vracia textový retazec alebo Undefined ak bolo stlačené tlačidlo zrušiť.</li>
|
||||
<li><b>getInt(label = "", value = 0)</b> ukáže diálog pre získanie celočíselnej hodnoty. Prvý parameter je textový popis. Druhý parameter je východzia hodnota. Obydva parametre sú voliteľné. Vracia zadané číslo alebo ak je stlačené tlačidlo zrušiť vráti Undefined.</li>
|
||||
<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>
|
||||
V poli <b>files</b> sú inštancie objektu typu <b>File</b> ktorý ma nasledovné metódy.
|
||||
<ul>
|
||||
<li><b>fileName()</b> returns the name of the file, excluding the path.</li>
|
||||
<li><b>absoluteFilePath()</b> returns an absolute path including the file name.</li>
|
||||
<li><b>absolutePath()</b> returns an absolute path without the file name</li>
|
||||
<li><b>relativeFilePath()</b> return relative path including file name relative to directory that was in list of directories to be scanned. For example you add C:/images as input directory. In this directory there
|
||||
is file <i>C:/images/lights/red/M42_001.fits</i> then this method will return <i>lights/red/M42_001.fits</i></li>
|
||||
<li><b>relativePath()</b> return same path as previous method just without file name. <i>lights/red</i></li>
|
||||
<li><b>baseName()</b> return file name up to the first dot. For example for <i>some.file.name.fits</i> it will return <i>some</i></li>
|
||||
<li><b>completeBaseName()</b> return file name up to the last dot. For example for <i>some.file.name.fits</i> it will return <i>some.file.name</i></li>
|
||||
<li><b>suffix()</b> return string after last dot in file name. For example <i>fits</i></li>
|
||||
<li><b>size()</b> return size of file in bytes.</li>
|
||||
<li><b>fitsKeywords()</b> return array of strings with every keyword that is in header. <i>SIMPLE,BITPIX,NAXIS,NAXIS1,NAXIS2,EXTEND,COMMENT</i></li>
|
||||
<li><b>fitsValue(key)</b> return value for keyword. In case that there is multiple occurrences it return last one.</li>
|
||||
<li><b>fitsValues(key)</b> return array of values for keyword.</li>
|
||||
<li><b>fitsRecords()</b> return array of objects with properties <b>key, value</b> and <b>comment</b> </li>
|
||||
<li><b>modifyFITSRecords(FITSRecordModify)</b> modify FITS header by adding, removing or updating FITS record. Return true on success. Refer to <i>FITSRecordModify</i></li>
|
||||
<li><b>isMarked()</b> return true if file is marked.</li>
|
||||
<li><b>copy(newpath)</b> copy file to new location. It return instance of new <i>File<i> object that represent this copied file. This path can be relative or absolute. In case that <i>newpath</i> parameter is relative
|
||||
path then it "Output directory" from GUI windows is used as base directory. Parameter <i>newpath</i> can absolute path. File is then copied to this path. In case that copy fail it return null.</li>
|
||||
<li><b>move(newpath)</b> move file to new location. It return false if move failed. This can happend if destination is not writable but also if destination file already exist. This functions does not overwrite existing file.
|
||||
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", "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.
|
||||
It return new instance of <i>File</i> that point to converted file.
|
||||
<pre>file.convert("converted_file.xisf", "xisf", {"compressionType": "zstd+sh", "compressionLevel": 70});
|
||||
file.convert("converted_file.fits", "fits", {"compressionType": "rice"});
|
||||
file.convert("converted_file.jpg", "png");</pre>
|
||||
</li>
|
||||
<li><b>convertAsync(outpath, format, params)</b> same as previous method but it does conversion in separated thread asynchronously and in parallel. Before calling any method on object returned by this method you must call
|
||||
<code>core.sync();</code> to ensure that conversion is done and destination file exists.
|
||||
<pre>let compression = {"compressionType": "zstd+sh"};
|
||||
let convertedFiles = [];
|
||||
for(file of files)
|
||||
{
|
||||
if(file.suffix() == "fits")
|
||||
convertedFiles.push(file.convertAsync("xisf/" + file.fileName(), "XISF", compression));
|
||||
}
|
||||
core.sync(); // ensure that files exist
|
||||
for(file of convertedFiles)// now we can iterate over the files
|
||||
{
|
||||
core.log(file.fileName() + " " + file.size()); // let print compressed file sizes
|
||||
}</pre></li>
|
||||
<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>
|
||||
This class is used to define modify operation FITS header in FITS and XISF files. It can remove update and add records. Order of operation is also remove then update and last add.
|
||||
The keyword names may be up to 8 characters long and can only contain uppercase letters, the digits 0-9, the hyphen, and the underscore character.
|
||||
<pre>let modify = new FITSRecordModify();
|
||||
modify.updateKeyword("OBJECT", "M42");
|
||||
modify.updateKeyword("MYTILE", "PART1", "adding custom keyword so WBPP can group it");
|
||||
modify.removeKeyword("OBJECT");
|
||||
// doesn't matter that it is specified as last. This will first remove
|
||||
// existing OBJECT record and then add again OBJECT=M42
|
||||
for(file in files)
|
||||
{
|
||||
file.modifyFITSRecords(modify);
|
||||
}</pre>
|
||||
|
||||
<ul>
|
||||
<li><b>new FITSRecordModify()</b> create new instance of object.</li>
|
||||
<li><b>removeKeyword(key);</b> specify removing of record with <i>key</i> as keyword.</li>
|
||||
<li><b>updateKeyword(key, value, comment = "")</b> specify updating existing keyword with value and comment. Comment is optional parameter. If record with keyword doesn't exist then it will add new one.
|
||||
Unless you want to have multiple records with same keyword (for example HISTORY) always use this method and not addKeyword.</li>
|
||||
<li><b>addKeyword(key, value, comment = "")</b> specify adding new keyword</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<p><small>PS: Kanji v ikone programu znamená "astronomia" v Japončine</small></p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
#include "batchprocessing.h"
|
||||
#include "ui_batchprocessing.h"
|
||||
#include <functional>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
#include <QCloseEvent>
|
||||
#include <QMessageBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QInputDialog>
|
||||
#include "scriptengine.h"
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <QCloseEvent>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusMessage>
|
||||
#endif
|
||||
|
||||
QList<QPair<QString, QString>> scanDirectories(const QStringList &paths)
|
||||
{
|
||||
QList<QPair<QString, QString>> files;
|
||||
QStringList scannedDirs;
|
||||
|
||||
std::function<void(const QString &root, const QString &path)> scanDirectory = [&](const QString &root, const QString &path)
|
||||
{
|
||||
QFileInfo info(path);
|
||||
if(info.isDir() && !scannedDirs.contains(info.canonicalFilePath()))
|
||||
{
|
||||
scannedDirs.append(info.canonicalFilePath());
|
||||
QDir dir(path);
|
||||
QStringList entries = dir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||
for(QString &entry : entries)
|
||||
scanDirectory(root, dir.absoluteFilePath(entry));
|
||||
}
|
||||
else if(info.isFile())
|
||||
{
|
||||
if(path == root)
|
||||
files.append({path, info.absolutePath()});
|
||||
else
|
||||
files.append({path, root});
|
||||
}
|
||||
};
|
||||
|
||||
for(const QString &path : paths)
|
||||
scanDirectory(path, path);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
void BatchProcessing::scanScriptDir()
|
||||
{
|
||||
QString current;
|
||||
if(_ui->scriptsList->currentItem())
|
||||
current = _ui->scriptsList->currentItem()->text();
|
||||
|
||||
_ui->scriptsList->clear();
|
||||
QDir dir(_scriptBasePath);
|
||||
QDir embededDir(":/scripts");
|
||||
QStringList scripts = dir.entryList(QDir::Files | QDir::Readable);
|
||||
scripts.append(embededDir.entryList(QDir::Files));
|
||||
scripts.removeDuplicates();
|
||||
_ui->scriptsList->addItems(scripts);
|
||||
|
||||
int idx = scripts.indexOf(current);
|
||||
if(idx>=0)_ui->scriptsList->setCurrentRow(idx);
|
||||
}
|
||||
|
||||
BatchProcessing::BatchProcessing(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
_ui = new Ui::BatchProcessing;
|
||||
_ui->setupUi(this);
|
||||
|
||||
QStringList scriptsPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||
if(scriptsPath.size())
|
||||
{
|
||||
QDir dir(scriptsPath.first());
|
||||
if(!dir.exists("scripts"))
|
||||
{
|
||||
if(!dir.mkpath("scripts"))
|
||||
qWarning() << "Failed to create scripts directory";
|
||||
}
|
||||
dir.cd("scripts");
|
||||
|
||||
_scriptBasePath = dir.absolutePath() + "/";
|
||||
scanScriptDir();
|
||||
_fileWatcher.addPath(_scriptBasePath);
|
||||
connect(&_fileWatcher, &QFileSystemWatcher::directoryChanged, this, &BatchProcessing::scanScriptDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Failed to get app data location";
|
||||
}
|
||||
|
||||
connect(_ui->addFilesButton, &QPushButton::released, this, &BatchProcessing::addFiles);
|
||||
connect(_ui->addDirButton, &QPushButton::released, this, &BatchProcessing::addDir);
|
||||
connect(_ui->removeButton, &QPushButton::released, this, &BatchProcessing::removePath);
|
||||
connect(_ui->removeAllButton, &QPushButton::released, this, &BatchProcessing::removeAllPaths);
|
||||
connect(_ui->startButton, &QPushButton::released, this, &BatchProcessing::runScript);
|
||||
connect(_ui->stopButton, &QPushButton::released, this, &BatchProcessing::stopScript);
|
||||
connect(_ui->browseButton, &QPushButton::released, this, &BatchProcessing::browse);
|
||||
connect(_ui->openScriptsButton, &QPushButton::released, this, &BatchProcessing::openScriptDir);
|
||||
|
||||
_textColor = _ui->log->palette().text().color();
|
||||
|
||||
QSettings settings;
|
||||
_ui->outputPath->setText(settings.value("batchprocessing/outputpath", QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).first()).toString());
|
||||
}
|
||||
|
||||
BatchProcessing::~BatchProcessing()
|
||||
{
|
||||
delete _engineThread;
|
||||
QSettings settings;
|
||||
settings.setValue("batchprocessing/outputpath", _ui->outputPath->text());
|
||||
delete _ui;
|
||||
}
|
||||
|
||||
void BatchProcessing::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
if(_engineThread)
|
||||
{
|
||||
QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Interrupt running script?"), tr("Interrupt running script?"));
|
||||
if(ret == QMessageBox::StandardButton::Yes)
|
||||
{
|
||||
_engineThread->interrupt();
|
||||
event->accept();
|
||||
}
|
||||
else
|
||||
{
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void BatchProcessing::addFiles()
|
||||
{
|
||||
QSettings settings;
|
||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files"), settings.value("batchprocessing/inputpath", QDir::homePath()).toString());
|
||||
if(!files.isEmpty())
|
||||
{
|
||||
_ui->pathsList->addItems(files);
|
||||
settings.setValue("batchprocessing/inputpath", QFileInfo(files.first()).absolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
void BatchProcessing::addDir()
|
||||
{
|
||||
QSettings settings;
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Select directory"), settings.value("batchprocessing/inputpath", QDir::homePath()).toString());
|
||||
if(!dir.isEmpty())
|
||||
{
|
||||
_ui->pathsList->addItem(dir);
|
||||
settings.setValue("batchprocessing/inputpath", dir);
|
||||
}
|
||||
}
|
||||
|
||||
void BatchProcessing::removePath()
|
||||
{
|
||||
for(auto &item : _ui->pathsList->selectedItems())
|
||||
delete item;
|
||||
}
|
||||
|
||||
void BatchProcessing::removeAllPaths()
|
||||
{
|
||||
_ui->pathsList->clear();
|
||||
}
|
||||
|
||||
void BatchProcessing::browse()
|
||||
{
|
||||
QString output = QFileDialog::getExistingDirectory(this, tr("Select output directory"), _ui->outputPath->text());
|
||||
if(!output.isEmpty())
|
||||
_ui->outputPath->setText(output);
|
||||
}
|
||||
|
||||
void BatchProcessing::openScriptDir()
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
QDBusConnection con = QDBusConnection::sessionBus();
|
||||
QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.FileManager1", "/org/freedesktop/FileManager1", "org.freedesktop.FileManager1", "ShowFolders");
|
||||
QList<QVariant> args = {QStringList(QUrl::fromLocalFile(_scriptBasePath).toString()), QString()};
|
||||
message.setArguments(args);
|
||||
con.call(message);
|
||||
#endif
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QProcess::startDetached("explorer.exe", {QDir::toNativeSeparators(_scriptBasePath)});
|
||||
#endif
|
||||
#ifdef Q_OS_MACOS
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(_scriptBasePath));
|
||||
#endif
|
||||
}
|
||||
|
||||
void BatchProcessing::runScript()
|
||||
{
|
||||
_ui->log->clear();
|
||||
auto selectedItems = _ui->scriptsList->selectedItems();
|
||||
if(selectedItems.size())
|
||||
{
|
||||
_engineThread = new Script::ScriptEngineThread(this);
|
||||
connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessage);
|
||||
connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::scriptFinished);
|
||||
QStringList paths;
|
||||
for(int i=0; i<_ui->pathsList->count(); i++)
|
||||
paths.append(_ui->pathsList->item(i)->text());
|
||||
|
||||
QFileInfo outDir(_ui->outputPath->text());
|
||||
if(outDir.exists() && outDir.isWritable())
|
||||
{
|
||||
QString script = selectedItems.first()->text();
|
||||
if(QDir(_scriptBasePath).exists(script))
|
||||
script = _scriptBasePath + script;
|
||||
else
|
||||
script = ":/scripts/" + script;
|
||||
|
||||
_engineThread->setParams(script, scanDirectories(paths), _ui->outputPath->text());
|
||||
_engineThread->start();
|
||||
_ui->startButton->setEnabled(false);
|
||||
_ui->stopButton->setEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::warning(this, tr("Invalid output directory"), tr("Output directory path doesn't exist or is not writable"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BatchProcessing::stopScript()
|
||||
{
|
||||
qDebug() << "Stop script";
|
||||
if(_engineThread)
|
||||
_engineThread->interrupt();
|
||||
}
|
||||
|
||||
void BatchProcessing::scriptFinished()
|
||||
{
|
||||
_ui->startButton->setEnabled(true);
|
||||
_ui->stopButton->setEnabled(false);
|
||||
qDebug() << "script finished";
|
||||
_engineThread->deleteLater();
|
||||
_engineThread = nullptr;
|
||||
}
|
||||
|
||||
void BatchProcessing::newMessage(const QString &message, bool error)
|
||||
{
|
||||
if(error)_ui->log->setTextColor(Qt::red);
|
||||
else _ui->log->setTextColor(_textColor);
|
||||
_ui->log->append(message);
|
||||
}
|
||||
|
||||
QJSValue BatchProcessing::getString(const QString &label, const QString &text)
|
||||
{
|
||||
bool ok = false;
|
||||
QString ret = QInputDialog::getText(this, tr("Enter text"), label, QLineEdit::Normal, text, &ok);
|
||||
return ok ? ret : QJSValue();
|
||||
}
|
||||
|
||||
QJSValue BatchProcessing::getInt(const QString &label, int value)
|
||||
{
|
||||
bool ok = false;
|
||||
int ret = QInputDialog::getInt(this, tr("Enter integer number"), label, value, INT_MIN, INT_MAX, 1, &ok);
|
||||
return ok ? ret : QJSValue();
|
||||
}
|
||||
|
||||
QJSValue BatchProcessing::getFloat(const QString &label, double value, int decimals)
|
||||
{
|
||||
bool ok = false;
|
||||
double ret = QInputDialog::getDouble(this, tr("Enter float number"), label, value, -INFINITY, INFINITY, decimals, &ok);
|
||||
return ok ? ret : QJSValue();
|
||||
}
|
||||
|
||||
QJSValue BatchProcessing::getItem(const QStringList &items, const QString &label, int current)
|
||||
{
|
||||
bool ok = false;
|
||||
QString ret = QInputDialog::getItem(this, tr("Select item"), label, items, current, false, &ok);
|
||||
return ok ? ret : QJSValue();
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef BATCHPROCESSING_H
|
||||
#define BATCHPROCESSING_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFileSystemWatcher>
|
||||
#include "scriptengine.h"
|
||||
|
||||
namespace Ui { class BatchProcessing; }
|
||||
|
||||
class BatchProcessing : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
Ui::BatchProcessing *_ui;
|
||||
QString _scriptBasePath;
|
||||
QFileSystemWatcher _fileWatcher;
|
||||
Script::ScriptEngineThread *_engineThread = nullptr;
|
||||
QColor _textColor;
|
||||
private slots:
|
||||
void scanScriptDir();
|
||||
public:
|
||||
explicit BatchProcessing(QWidget *parent = nullptr);
|
||||
~BatchProcessing();
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event);
|
||||
public slots:
|
||||
void addFiles();
|
||||
void addDir();
|
||||
void removePath();
|
||||
void removeAllPaths();
|
||||
void browse();
|
||||
void openScriptDir();
|
||||
void runScript();
|
||||
void stopScript();
|
||||
void scriptFinished();
|
||||
void newMessage(const QString &message, bool error);
|
||||
|
||||
QJSValue getString(const QString &label, const QString &text);
|
||||
QJSValue getInt(const QString &label, int value);
|
||||
QJSValue getFloat(const QString &label, double value, int decimals);
|
||||
QJSValue getItem(const QStringList &items, const QString &label, int current);
|
||||
};
|
||||
|
||||
#endif // BATCHPROCESSING_H
|
||||
@@ -0,0 +1,226 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BatchProcessing</class>
|
||||
<widget class="QDialog" name="BatchProcessing">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1024</width>
|
||||
<height>768</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Batch Processing</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Input files and directories</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="pathsList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::MultiSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addFilesButton">
|
||||
<property name="text">
|
||||
<string>Add files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="addDirButton">
|
||||
<property name="text">
|
||||
<string>Add directories</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeButton">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeAllButton">
|
||||
<property name="text">
|
||||
<string>Remove all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Output directory</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="outputPath">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browseButton">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Scripts</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="openScriptsButton">
|
||||
<property name="text">
|
||||
<string>Open scripts</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="scriptsList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>1</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Log</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="log">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>4</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>FreeMono</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="startButton">
|
||||
<property name="text">
|
||||
<string>Start script</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="stopButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Stop script</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="text">
|
||||
<string>Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>closeButton</sender>
|
||||
<signal>released()</signal>
|
||||
<receiver>BatchProcessing</receiver>
|
||||
<slot>close()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>973</x>
|
||||
<y>745</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>511</x>
|
||||
<y>383</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -10,12 +10,12 @@ Database::Database(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool Database::init()
|
||||
bool Database::init(const QLatin1String &connectionName)
|
||||
{
|
||||
QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
QDir dir(path);
|
||||
|
||||
QSqlDatabase m_database = QSqlDatabase::addDatabase("QSQLITE");
|
||||
QSqlDatabase m_database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
|
||||
|
||||
if(!dir.mkpath("."))
|
||||
return false;
|
||||
@@ -154,25 +154,31 @@ int Database::checkVersion()
|
||||
return -1;
|
||||
}
|
||||
|
||||
static QStringList nameFilters = {"*.fit", "*.fits", "*.xisf"};
|
||||
static QStringList nameFilters = {"*.fit", "*.fits", "*.fz", "*.xisf"};
|
||||
|
||||
static int countFiles(const QDir &dir, int count = 0)
|
||||
static int countFiles(const QDir &dir, QStringList &scannedDirs)
|
||||
{
|
||||
count += dir.entryList(nameFilters, QDir::Files).size();
|
||||
if(scannedDirs.contains(dir.canonicalPath()))return 0;
|
||||
scannedDirs.append(dir.canonicalPath());
|
||||
|
||||
int count = dir.entryList(nameFilters, QDir::Files).size();
|
||||
QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
for(const QString &d : dirs)
|
||||
count += countFiles(dir.filePath(d));
|
||||
count += countFiles(dir.filePath(d), scannedDirs);
|
||||
return count;
|
||||
}
|
||||
|
||||
void Database::indexDir(const QDir &dir, QProgressDialog *progress)
|
||||
{
|
||||
m_progress = 0;
|
||||
int count = countFiles(dir);
|
||||
QStringList scannedDirs;
|
||||
int count = countFiles(dir, scannedDirs);
|
||||
progress->setMaximum(count);
|
||||
QSqlDatabase database = QSqlDatabase::database();
|
||||
database.transaction();
|
||||
if(indexDir2(dir, progress))
|
||||
|
||||
scannedDirs.clear();
|
||||
if(indexDir2(dir, progress, scannedDirs))
|
||||
{
|
||||
database.commit();
|
||||
emit databaseChanged();
|
||||
@@ -225,14 +231,17 @@ QStringList Database::getFitsKeywords()
|
||||
return keywords;
|
||||
}
|
||||
|
||||
bool Database::indexDir2(const QDir &dir, QProgressDialog *progress)
|
||||
bool Database::indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs)
|
||||
{
|
||||
if(scannedDirs.contains(dir.canonicalPath()))return true;
|
||||
scannedDirs.append(dir.canonicalPath());
|
||||
|
||||
QFileInfoList files = dir.entryInfoList(nameFilters, QDir::Files);
|
||||
QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
|
||||
for(const QString &d : dirs)
|
||||
{
|
||||
if(!indexDir2(dir.filePath(d), progress))
|
||||
if(!indexDir2(dir.filePath(d), progress, scannedDirs))
|
||||
return false;
|
||||
}
|
||||
for(const QFileInfo &file : files)
|
||||
|
||||
@@ -24,7 +24,7 @@ class Database : public QObject
|
||||
int m_progress;
|
||||
public:
|
||||
explicit Database(QObject *parent = 0);
|
||||
bool init();
|
||||
bool init(const QLatin1String &connectionName = QLatin1String(QSqlDatabase::defaultConnection));
|
||||
bool mark(const QString &filename);
|
||||
bool unmark(const QString &filename);
|
||||
bool mark(const QStringList &filenames);
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
void reindex(QProgressDialog *progress);
|
||||
QStringList getFitsKeywords();
|
||||
protected:
|
||||
bool indexDir2(const QDir &dir, QProgressDialog *progress);
|
||||
bool indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs);
|
||||
bool indexFile(const QFileInfo &file);
|
||||
bool checkError(QSqlQuery &query);
|
||||
int checkVersion();
|
||||
|
||||
@@ -201,7 +201,8 @@ void FITSFileModel::prepareQuery()
|
||||
if(lastError().type() != QSqlError::NoError)
|
||||
qDebug() << "Database error" << lastError();
|
||||
|
||||
m_markedFiles = m_database->getMarkedFiles().toSet();
|
||||
QStringList list = m_database->getMarkedFiles();
|
||||
m_markedFiles = QSet<QString>(list.begin(), list.end());
|
||||
}
|
||||
|
||||
DatabaseTableView::DatabaseTableView(QWidget *parent) : QTableView(parent)
|
||||
@@ -285,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);
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 1400–2000
|
||||
// index-4118.fits 1000–1400
|
||||
// index-4117.fits 680–1000
|
||||
// index-4116.fits 480–680
|
||||
// index-4115.fits 340–480
|
||||
// index-4114.fits 240–340
|
||||
// index-4113.fits 170–240
|
||||
// index-4112.fits 120–170
|
||||
// index-4111.fits 85–120
|
||||
// index-4110.fits 60—85
|
||||
// index-4109.fits 42–60
|
||||
// index-4108.fits 30–42
|
||||
// index-4107.fits 22–30
|
||||
// index-5206-*.fits 16–22
|
||||
// index-5205-*.fits 11–16
|
||||
// index-5204-*.fits 8–11
|
||||
// index-5203-*.fits 5.6–8.0
|
||||
// index-5202-*.fits 4.0–5.6
|
||||
// index-5201-*.fits 2.8–4.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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -48,6 +48,7 @@ FITSRecord::FITSRecord(const LibXISF::Property &property)
|
||||
key = property.id.c_str();
|
||||
value = QString::fromStdString(property.value.toString());
|
||||
comment = property.comment.c_str();
|
||||
xisf = true;
|
||||
}
|
||||
|
||||
QByteArray FITSRecord::valueToByteArray() const
|
||||
@@ -81,7 +82,7 @@ void ImageInfo::setInfo(const ImageInfoData &info)
|
||||
QTreeWidgetItem *fitsHeader = new QTreeWidgetItem({tr("FITS Header")});
|
||||
for(const FITSRecord &record : info.fitsHeader)
|
||||
{
|
||||
new QTreeWidgetItem(fitsHeader, {record.key, record.value.toString(), record.comment});
|
||||
new QTreeWidgetItem(fitsHeader, {record.key, record.value.toString().left(1024), record.comment});
|
||||
}
|
||||
addTopLevelItem(fitsHeader);
|
||||
}
|
||||
@@ -97,14 +98,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)
|
||||
{
|
||||
@@ -120,7 +121,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)
|
||||
{
|
||||
@@ -155,13 +156,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;
|
||||
|
||||
@@ -180,7 +181,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;
|
||||
|
||||
@@ -199,7 +200,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;
|
||||
|
||||
@@ -256,6 +257,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)
|
||||
{
|
||||
}
|
||||
@@ -279,7 +308,145 @@ QString SkyPoint::toString() const
|
||||
t = t.addSecs(ra * 240);
|
||||
|
||||
double deg, min, sec;
|
||||
min = std::modf(dec, °) * 60;
|
||||
min = std::abs(std::modf(dec, °) * 60);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ struct FITSRecord
|
||||
QByteArray key;
|
||||
QVariant value;
|
||||
QByteArray comment;
|
||||
bool xisf = false;
|
||||
bool editable() const;
|
||||
FITSRecord(){}
|
||||
FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment);
|
||||
@@ -30,11 +31,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;
|
||||
@@ -42,21 +57,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);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "imageringlist.h"
|
||||
#include <functional>
|
||||
#include <QThreadPool>
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include "loadrunable.h"
|
||||
#include "rawimage.h"
|
||||
#include "database.h"
|
||||
@@ -108,6 +111,14 @@ ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter,
|
||||
connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
|
||||
m_nameFilter.replaceInStrings(QRegularExpression("^"), "*.");
|
||||
m_thumbPool = new QThreadPool(this);
|
||||
|
||||
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()
|
||||
@@ -119,21 +130,39 @@ ImageRingList::~ImageRingList()
|
||||
m_thumbPool->waitForDone();
|
||||
}
|
||||
|
||||
bool ImageRingList::setDir(const QString path, const QString ¤tFile)
|
||||
bool ImageRingList::setDir(const QString path, const QString ¤tFile, bool recursive)
|
||||
{
|
||||
QDir dir(path);
|
||||
|
||||
if(dir.exists())
|
||||
{
|
||||
QDir::SortFlags sortFlags = m_liveMode ? QDir::Time : m_sort | QDir::IgnoreCase;
|
||||
if(m_reversed)sortFlags |= QDir::Reversed;
|
||||
QStringList list = dir.entryList(m_nameFilter, QDir::Files | QDir::Readable, sortFlags);
|
||||
QStringList scannedDirs;
|
||||
QStringList absolutePaths;
|
||||
foreach(const QString &file, list)
|
||||
std::function<void(const QString&)> scanDir = [&](const QString &path)
|
||||
{
|
||||
absolutePaths.append(dir.absoluteFilePath(file));
|
||||
}
|
||||
setFiles(absolutePaths, m_liveMode ? list.first() : currentFile);
|
||||
QDir dir(path);
|
||||
if(scannedDirs.contains(dir.canonicalPath()))return;
|
||||
scannedDirs.append(dir.canonicalPath());
|
||||
QDir::SortFlags sortFlags = m_liveMode ? QDir::Time : m_sort | QDir::IgnoreCase;
|
||||
if(m_reversed)sortFlags |= QDir::Reversed;
|
||||
|
||||
if(recursive)
|
||||
{
|
||||
QStringList dirs = dir.entryList(QDir::Readable | QDir::Dirs | QDir::NoDotAndDotDot, sortFlags);
|
||||
for(const QString &subdir : dirs)
|
||||
scanDir(dir.absoluteFilePath(subdir));
|
||||
}
|
||||
|
||||
QStringList list = dir.entryList(m_nameFilter, QDir::Files | QDir::Readable, sortFlags);
|
||||
for(const QString &file : list)
|
||||
{
|
||||
absolutePaths.append(dir.absoluteFilePath(file));
|
||||
}
|
||||
};
|
||||
|
||||
scanDir(path);
|
||||
qDebug() << absolutePaths.size();
|
||||
setFiles(absolutePaths, m_liveMode ? absolutePaths.first() : currentFile);
|
||||
|
||||
m_fileSystemWatcher.removePaths(m_fileSystemWatcher.directories());
|
||||
m_fileSystemWatcher.addPath(path);
|
||||
@@ -146,7 +175,7 @@ void ImageRingList::setFile(const QString &file)
|
||||
{
|
||||
QFileInfo info(file);
|
||||
if(info.isDir())
|
||||
setDir(file);
|
||||
setDir(file, QString(), true);
|
||||
else
|
||||
setDir(info.absolutePath(), file);
|
||||
}
|
||||
@@ -163,6 +192,10 @@ void ImageRingList::increment()
|
||||
{
|
||||
if(m_images.size())
|
||||
{
|
||||
//don't increment if current image was not loaded yet
|
||||
if(!(*m_currImage)->rawImage())
|
||||
return;
|
||||
|
||||
(*m_firstImage)->release();
|
||||
m_firstImage = increment(m_firstImage);
|
||||
m_currImage = increment(m_currImage);
|
||||
@@ -185,6 +218,16 @@ void ImageRingList::decrement()
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -295,11 +338,13 @@ void ImageRingList::clearThumbnails()
|
||||
|
||||
QModelIndex ImageRingList::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return createIndex(row, column, m_images.at(row).get());
|
||||
}
|
||||
|
||||
QModelIndex ImageRingList::parent(const QModelIndex &child) const
|
||||
{
|
||||
Q_UNUSED(child);
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
@@ -313,6 +358,7 @@ int ImageRingList::rowCount(const QModelIndex &parent) const
|
||||
|
||||
int ImageRingList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -398,6 +444,20 @@ void ImageRingList::reverseSort()
|
||||
}
|
||||
}
|
||||
|
||||
void ImageRingList::toggleSlideshow(bool start)
|
||||
{
|
||||
if(start)
|
||||
{
|
||||
QSettings settings;
|
||||
int time = settings.value("settings/slideshowtime", 1.0).toDouble() * 1000;
|
||||
m_slideShowTimer->start(time);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_slideShowTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageRingList::setFiles(const QStringList files, const QString ¤tFile)
|
||||
{
|
||||
QThreadPool::globalInstance()->clear();
|
||||
@@ -451,13 +511,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());
|
||||
}
|
||||
|
||||
@@ -65,14 +65,15 @@ class ImageRingList : public QAbstractItemModel
|
||||
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;
|
||||
bool setDir(const QString path, const QString ¤tFile = QString());
|
||||
bool setDir(const QString path, const QString ¤tFile = QString(), bool recursive = false);
|
||||
void setFile(const QString &file);
|
||||
ImagePtr currentImage();
|
||||
void increment();
|
||||
void decrement();
|
||||
void setLiveMode(bool live);
|
||||
void setCalculateStats(bool stats);
|
||||
void setFindPeaks(bool findPeaks);
|
||||
@@ -96,6 +97,10 @@ public slots:
|
||||
void setPreload(int width);
|
||||
void setSort(QDir::SortFlag sort);
|
||||
void reverseSort();
|
||||
void toggleSlideshow(bool start);
|
||||
void increment();
|
||||
void decrement();
|
||||
void setMarked();
|
||||
protected:
|
||||
void setFiles(const QStringList files, const QString ¤tFile = QString());
|
||||
QList<ImagePtr>::iterator increment(QList<ImagePtr>::iterator iter);
|
||||
@@ -108,6 +113,7 @@ signals:
|
||||
protected slots:
|
||||
void imageLoaded(Image *image);
|
||||
void dirChanged(QString dir);
|
||||
void reloadDir();
|
||||
};
|
||||
|
||||
#endif // IMAGERINGLIST_H
|
||||
|
||||
@@ -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->posF()) / 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->posF();
|
||||
horizontalScrollBar()->setValue(top.x());
|
||||
verticalScrollBar()->setValue(top.y());
|
||||
m_imageWidget->thumbnailLoaded(image);
|
||||
}
|
||||
|
||||
void ImageScrollArea::setMTFParams(const MTFParam ¶ms)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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 ¶ms);
|
||||
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
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
#include "imagescrollareagl.h"
|
||||
#include <QOpenGLFunctions>
|
||||
#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"
|
||||
|
||||
int FILTERING = 1;
|
||||
bool OpenGLES = false;
|
||||
const int LUT_SIZE = 32;
|
||||
|
||||
struct RawImageType
|
||||
{
|
||||
@@ -28,7 +29,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;
|
||||
@@ -46,6 +47,17 @@ RawImageType getRawImageType(const RawImage *img)
|
||||
else
|
||||
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;
|
||||
}
|
||||
|
||||
if(img->channels() >= 3)
|
||||
@@ -56,8 +68,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);
|
||||
@@ -69,35 +81,58 @@ 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)
|
||||
{
|
||||
if(image == nullptr)return;
|
||||
m_currentImg = index;
|
||||
|
||||
if(!image || !image->valid())
|
||||
{
|
||||
m_imgWidth = 0;
|
||||
m_imgHeight = 0;
|
||||
m_error = tr("Failed to load image");
|
||||
m_rawImage.reset();
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
m_error.clear();
|
||||
makeCurrent();
|
||||
m_rawImage = image;
|
||||
m_rawImage->downscaleTo(m_maxTextureSize);
|
||||
if((int)image->width() > m_maxTextureSize || (int)image->height() > m_maxTextureSize)
|
||||
{
|
||||
uint32_t newW = std::min(image->width() * m_maxTextureSize / image->width(), image->width() * m_maxTextureSize / image->height());
|
||||
uint32_t newH = std::min(image->height() * m_maxTextureSize / image->width(), image->height() * m_maxTextureSize / image->height());
|
||||
m_rawImage->resize(newW, newH);
|
||||
}
|
||||
|
||||
m_imgWidth = image->width();
|
||||
m_imgHeight = image->height();
|
||||
m_currentImg = index;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
m_image->destroy();
|
||||
@@ -108,23 +143,19 @@ void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
|
||||
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();
|
||||
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_unit_scale[0] = 1.0f;
|
||||
m_unit_scale[1] = 0.0f;
|
||||
auto &stats = image->imageStats();
|
||||
if(image->type() == RawImage::FLOAT32 || image->type() == RawImage::FLOAT64)
|
||||
if(image->type() == RawImage::FLOAT32)
|
||||
{
|
||||
float min = *std::min_element(stats.m_min, stats.m_min + 4);
|
||||
float max = *std::max_element(stats.m_max, stats.m_max + 4);
|
||||
if(min < 0.0f || max > 1.0f)
|
||||
{
|
||||
m_unit_scale[0] = 1.0f / (max - min);
|
||||
m_unit_scale[1] = min * m_unit_scale[0];
|
||||
}
|
||||
auto unitScaling = image->unitScale();
|
||||
m_unit_scale[0] = unitScaling.first;
|
||||
m_unit_scale[1] = unitScaling.second;
|
||||
}
|
||||
|
||||
if(m_debayerTex)
|
||||
@@ -137,12 +168,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)
|
||||
@@ -166,20 +197,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::blockRepaint(bool block)
|
||||
{
|
||||
m_blockRepaint = block;
|
||||
if(!block)update();
|
||||
}
|
||||
|
||||
void ImageWidget::allocateThumbnails(const QStringList &paths)
|
||||
void ImageWidgetGL::allocateThumbnails(const QStringList &paths)
|
||||
{
|
||||
makeCurrent();
|
||||
int count = paths.size();
|
||||
@@ -193,14 +218,17 @@ 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(paths.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;
|
||||
@@ -213,13 +241,25 @@ QVector2D ImageWidget::getImagePixelCoord(const QVector2D &pos)
|
||||
return (pos + offset) / m_scale;
|
||||
}
|
||||
|
||||
void ImageWidget::setMTFParams(const MTFParam ¶ms)
|
||||
void ImageWidgetGL::setBayerMask(int mask)
|
||||
{
|
||||
m_firstRed[0] = mask & 0x1;
|
||||
m_firstRed[1] = (mask & 0x2) >> 1;
|
||||
if(m_debayerTex)
|
||||
{
|
||||
f->glDeleteTextures(1, &m_debayerTex);
|
||||
m_debayerTex = 0;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void ImageWidgetGL::setMTFParams(const MTFParam ¶ms)
|
||||
{
|
||||
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)
|
||||
@@ -230,25 +270,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();
|
||||
@@ -256,6 +296,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);
|
||||
@@ -263,21 +304,30 @@ 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;
|
||||
|
||||
makeCurrent();
|
||||
const RawImage *raw = image->thumbnail();
|
||||
if(!raw)return;
|
||||
m_thumbnailTexture->setData(0, image->number(), QOpenGLTexture::RGBA, QOpenGLTexture::UInt16, raw->data(), m_transferOptions.get());
|
||||
if(!raw || !raw->valid())return;
|
||||
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;
|
||||
@@ -285,16 +335,14 @@ 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 ImageWidgetGL::paintGL()
|
||||
{
|
||||
if(m_blockRepaint)return;
|
||||
|
||||
float dx = m_dx;
|
||||
float dy = m_dy;
|
||||
if(m_width > m_image->width() * m_scale)
|
||||
@@ -303,6 +351,7 @@ void ImageWidget::paintGL()
|
||||
dy = -height() * 0.5f + m_image->height() * m_scale * 0.5f;
|
||||
QBrush highlight = style()->standardPalette().highlight();
|
||||
|
||||
f->glClear(GL_COLOR_BUFFER_BIT);
|
||||
if(m_showThumbnails)
|
||||
{
|
||||
m_vaoThumb->bind();
|
||||
@@ -328,15 +377,21 @@ 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;
|
||||
for(int i=0; i < m_thumbnailCount; i++)
|
||||
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;
|
||||
float y = i / w * THUMB_SIZE_BORDER_Y + THUMB_SIZE - m_dy + off;
|
||||
@@ -361,13 +416,23 @@ void ImageWidget::paintGL()
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!m_error.isEmpty())
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setPen(Qt::red);
|
||||
painter.setFont(QFont("Sans", 24, QFont::Bold));
|
||||
painter.drawText(0, 0, width(), height(), Qt::AlignCenter | Qt::AlignHCenter, m_error);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_vao->bind();
|
||||
debayer();
|
||||
|
||||
m_vao->bind();
|
||||
if(m_superpixel && m_debayerTex)
|
||||
{
|
||||
f->glActiveTexture(GL_TEXTURE0);
|
||||
f->glBindTexture(GL_TEXTURE_2D, m_debayerTex);
|
||||
}
|
||||
else
|
||||
m_image->bind(0);
|
||||
|
||||
@@ -380,15 +445,16 @@ void ImageWidget::paintGL()
|
||||
m_program->setUniformValue("bw", m_bwImg && !m_superpixel);
|
||||
m_program->setUniformValue("false_color", m_falseColor && m_bwImg);
|
||||
m_program->setUniformValue("invert", m_invert);
|
||||
#ifdef COLOR_MANAGMENT
|
||||
m_program->setUniformValue("filtering", m_scale > 1.0f ? FILTERING : 1);
|
||||
m_program->setUniformValue("lut_table", 2);
|
||||
m_program->setUniformValue("srgb", m_srgb);
|
||||
#endif
|
||||
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;
|
||||
@@ -397,15 +463,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 = context()->versionFunctions<QOpenGLFunctions_3_3_Core>();
|
||||
|
||||
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();
|
||||
@@ -416,9 +506,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);
|
||||
@@ -433,9 +523,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();
|
||||
@@ -444,8 +534,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, ":/shaders/image.vert");
|
||||
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/image.frag");
|
||||
m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, loadShader(":/image.vert"));
|
||||
m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, loadShader(":/image.frag"));
|
||||
|
||||
if(!m_program->link())
|
||||
{
|
||||
@@ -458,11 +548,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, ":/shaders/debayer.vert");
|
||||
m_debayerProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/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");
|
||||
@@ -478,8 +569,8 @@ void ImageWidget::initializeGL()
|
||||
m_vaoThumb->bind();
|
||||
|
||||
m_thumbnailProgram = std::unique_ptr<QOpenGLShaderProgram>(new QOpenGLShaderProgram);
|
||||
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/thumb.vert");
|
||||
m_thumbnailProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/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");
|
||||
@@ -499,8 +590,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);
|
||||
@@ -510,27 +602,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))
|
||||
{
|
||||
@@ -547,7 +638,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;
|
||||
@@ -559,11 +650,11 @@ void ImageWidget::mousePressEvent(QMouseEvent *event)
|
||||
else
|
||||
{
|
||||
if(event->button() == Qt::LeftButton)
|
||||
m_lastPos = event->localPos();
|
||||
m_lastPos = event->position();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
void ImageWidgetGL::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if(m_selecting)
|
||||
{
|
||||
@@ -571,8 +662,8 @@ void ImageWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
}
|
||||
else if(!m_lastPos.isNull())
|
||||
{
|
||||
QPointF off = event->localPos() - m_lastPos;
|
||||
m_lastPos = event->localPos();
|
||||
QPointF off = event->position() - m_lastPos;
|
||||
m_lastPos = event->position();
|
||||
setOffset(m_dx - off.x(), m_dy - off.y());
|
||||
return;
|
||||
}
|
||||
@@ -598,7 +689,7 @@ void ImageWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void ImageWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
void ImageWidgetGL::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if(m_selecting)
|
||||
{
|
||||
@@ -629,7 +720,7 @@ void ImageWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void ImageWidget::wheelEvent(QWheelEvent *event)
|
||||
void ImageWidgetGL::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
if(m_showThumbnails)
|
||||
{
|
||||
@@ -638,11 +729,11 @@ void ImageWidget::wheelEvent(QWheelEvent *event)
|
||||
else
|
||||
{
|
||||
if(std::abs(event->angleDelta().y()) > 15)
|
||||
zoom(event->angleDelta().y(), event->modifiers() & Qt::ShiftModifier ? QPointF() : event->posF());
|
||||
zoom(event->angleDelta().y(), event->modifiers() & Qt::ShiftModifier ? QPointF() : event->position());
|
||||
}
|
||||
}
|
||||
|
||||
void ImageWidget::thumbSelect(QMouseEvent *event)
|
||||
void ImageWidgetGL::thumbSelect(QMouseEvent *event)
|
||||
{
|
||||
QPoint p = event->pos();
|
||||
const int off = (THUMB_SIZE_BORDER - THUMB_SIZE) / 2;
|
||||
@@ -671,16 +762,17 @@ 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);
|
||||
|
||||
m_debayerProgram->bind();
|
||||
f->glUniform2i(m_debayerProgram->uniformLocation("firstRed"), m_firstRed[0], m_firstRed[1]);
|
||||
m_image->bind(0);
|
||||
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
@@ -688,104 +780,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());
|
||||
}
|
||||
@@ -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 ¶ms) = 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;
|
||||
@@ -53,7 +75,6 @@ class ImageWidget : public QOpenGLWidget
|
||||
float m_scale = 1.0f;
|
||||
int m_scaleStop = 0;
|
||||
bool m_bestFit = false;
|
||||
bool m_blockRepaint = false;
|
||||
bool m_bwImg = false;
|
||||
bool m_falseColor = false;
|
||||
bool m_invert = false;
|
||||
@@ -65,29 +86,29 @@ class ImageWidget : public QOpenGLWidget
|
||||
int m_thumbnailCount = 0;
|
||||
int m_maxTextureSize = 0;
|
||||
int m_maxArrayLayers = 0;
|
||||
int m_firstRed[2] = {0, 0};
|
||||
QVector<ImageThumb> m_thumnails;
|
||||
Database *m_database = nullptr;
|
||||
QPointF m_lastPos;
|
||||
QString m_error;
|
||||
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 blockRepaint(bool block);
|
||||
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);
|
||||
public slots:
|
||||
void setMTFParams(const MTFParam ¶ms);
|
||||
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 ¶ms) 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;
|
||||
@@ -107,26 +128,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,5 +1,5 @@
|
||||
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
|
||||
find_program(XDG-ICON-RESOURCE_EXECUTABLE xdg-icon-resource)
|
||||
execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor space.nouspiro.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 space.nouspiro.tenmon.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 128 space.nouspiro.tenmon_128.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 resources/space.nouspiro.tenmon.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 128 resources/space.nouspiro.tenmon_128.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
@@ -7,16 +7,13 @@
|
||||
#include <QElapsedTimer>
|
||||
#include <QDebug>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <libexif/exif-data.h>
|
||||
#include <fitsio2.h>
|
||||
#include <libxisf.h>
|
||||
#include "rawimage.h"
|
||||
#include "starfit.h"
|
||||
#include "wcslib/wcshdr.h"
|
||||
|
||||
#ifdef COLOR_MANAGMENT
|
||||
#include <QColorSpace>
|
||||
#endif
|
||||
#include <lcms2.h>
|
||||
|
||||
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) :
|
||||
m_file(file),
|
||||
@@ -179,30 +176,33 @@ 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;
|
||||
int type;
|
||||
fits_open_image(&file, path.toLocal8Bit().data(), READONLY, &status);
|
||||
fits_get_hdu_type(file, &type, &status);
|
||||
int type = -1;
|
||||
fits_open_diskfile(&file, path.toLocal8Bit().data(), READONLY, &status);
|
||||
int num = 0;
|
||||
fits_get_num_hdus(file, &num, &status);
|
||||
|
||||
if(type == IMAGE_HDU)
|
||||
int imgtype;
|
||||
int naxis;
|
||||
long naxes[3] = {0};
|
||||
for(int i=1; i <= num; i++)
|
||||
{
|
||||
int imgtype;
|
||||
int naxis;
|
||||
long naxes[3] = {0};
|
||||
fits_movabs_hdu(file, i, IMAGE_HDU, &status);
|
||||
fits_get_hdu_type(file, &type, &status);
|
||||
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
|
||||
fits_get_img_equivtype(file, &imgtype, &status);
|
||||
|
||||
if(naxis >= 2 && naxis <= 3 && status == 0)
|
||||
if(type == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
|
||||
{
|
||||
RawImage::DataType type;
|
||||
int fitstype;
|
||||
@@ -261,19 +261,27 @@ 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;
|
||||
}
|
||||
}
|
||||
noload:
|
||||
if(file)
|
||||
loadFITSHeader(file, info);
|
||||
|
||||
if(image)
|
||||
{
|
||||
for(auto fits : info.fitsHeader)
|
||||
{
|
||||
if(fits.key == "ROWORDER" && fits.value == "BOTTOM-UP")
|
||||
image->flip();
|
||||
}
|
||||
}
|
||||
|
||||
fits_close_file(file, &status);
|
||||
if(status)
|
||||
{
|
||||
@@ -286,7 +294,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
|
||||
{
|
||||
@@ -306,7 +314,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();
|
||||
@@ -328,16 +336,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)
|
||||
{
|
||||
@@ -363,46 +380,18 @@ void LoadRunable::run()
|
||||
info.info.append({QObject::tr("Filename"), finfo.fileName()});
|
||||
|
||||
std::shared_ptr<RawImage> rawImage;
|
||||
timer.start();
|
||||
if(m_file.endsWith(".CR2", Qt::CaseInsensitive) || m_file.endsWith(".NEF", Qt::CaseInsensitive) || m_file.endsWith(".DNG", Qt::CaseInsensitive))
|
||||
{
|
||||
loadRAW(m_file, info, rawImage);
|
||||
qDebug() << "LoadRAW" << timer.elapsed();
|
||||
}
|
||||
else if(m_file.endsWith(".FIT", Qt::CaseInsensitive) || m_file.endsWith(".FITS", Qt::CaseInsensitive))
|
||||
{
|
||||
loadFITS(m_file, info, rawImage);
|
||||
qDebug() << "LoadFITS" << timer.elapsed();
|
||||
}
|
||||
else if(m_file.endsWith(".XISF", Qt::CaseInsensitive))
|
||||
{
|
||||
loadXISF(m_file, info, rawImage);
|
||||
qDebug() << "LoadXISF" << timer.elapsed();
|
||||
}
|
||||
else
|
||||
{
|
||||
QImage img(m_file);
|
||||
#ifdef COLOR_MANAGMENT
|
||||
if(img.colorSpace().isValid() && img.colorSpace() != QColorSpace::SRgb)
|
||||
img.convertToColorSpace(QColorSpace::SRgb);
|
||||
#endif
|
||||
if(!loadImage(m_file, info, rawImage))
|
||||
info.info.append({QObject::tr("Error"), QObject::tr("Failed to load image")});
|
||||
|
||||
ExifData *exif = exif_data_new_from_file(m_file.toLocal8Bit().constData());
|
||||
info.info.append({QObject::tr("Width"), QString::number(img.width())});
|
||||
info.info.append({QObject::tr("Height"), QString::number(img.height())});
|
||||
if(exif)
|
||||
{
|
||||
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_ISO_SPEED_RATINGS);
|
||||
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_SHUTTER_SPEED_VALUE);
|
||||
exif_data_free(exif);
|
||||
}
|
||||
rawImage = std::make_shared<RawImage>(img);
|
||||
qDebug() << "LoadQImage" << timer.elapsed();
|
||||
}
|
||||
|
||||
if(rawImage /*&& m_analyzeLevel >= Statistics*/ && !m_thumbnail)
|
||||
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();
|
||||
@@ -428,68 +417,33 @@ void LoadRunable::run()
|
||||
.arg(100.0 * stats.m_saturated[1] / rawImage->size())
|
||||
.arg(100.0 * stats.m_saturated[2] / rawImage->size())});
|
||||
}
|
||||
|
||||
if(m_analyzeLevel >= Peaks)
|
||||
{
|
||||
std::vector<Peak> peaks;
|
||||
/*RawImage *medianImage = rawImage->medianFilter();
|
||||
qDebug() << "median" << timer.restart();
|
||||
int numPeaks = medianImage->findPeaks(median+stdDev*2, 20, peaks);
|
||||
delete medianImage;
|
||||
qDebug() << "peaks" << timer.restart();
|
||||
//if(m_analyzeLevel == Peaks)
|
||||
// drawPeaks(img, peaks);
|
||||
qDebug() << "draw peaks" << timer.restart();
|
||||
info.info.append({QObject::tr("Peaks"), QString::number(numPeaks)});
|
||||
//info.info.append({QObject::tr("Peaks draw"), QString::number(peaks.size())});
|
||||
|
||||
if(m_analyzeLevel>= Stars)
|
||||
{
|
||||
double fwhmX = 0;
|
||||
double fwhmY = 0;
|
||||
const int radius = 13;
|
||||
StarFit starFit(radius);
|
||||
std::vector<Star> stars;
|
||||
for(uint i=0; i<peaks.size(); i++)
|
||||
{
|
||||
Peak p = peaks[i];
|
||||
std::vector<double> r;
|
||||
int x = p.x();
|
||||
int y = p.y();
|
||||
rawImage->rect(x, y, radius, radius, r);
|
||||
Star star = starFit.fitStar(r, false);
|
||||
if(star.valid())
|
||||
{
|
||||
//printStarModel(radius, r, star);
|
||||
star.m_x += x;
|
||||
star.m_y += y;
|
||||
fwhmX += star.fwhmX();
|
||||
fwhmY += star.fwhmY();
|
||||
stars.push_back(star);
|
||||
}
|
||||
}
|
||||
//drawStars(img, stars);
|
||||
info.info.append({QObject::tr("FWHM X"), QString::number(fwhmX/stars.size())});
|
||||
info.info.append({QObject::tr("FWHM Y"), QString::number(fwhmY/stars.size())});
|
||||
}
|
||||
qDebug() << "Star fit" << timer.restart();*/
|
||||
}
|
||||
}
|
||||
|
||||
if(m_thumbnail)
|
||||
{
|
||||
if(rawImage)
|
||||
if(rawImage && rawImage->valid())
|
||||
{
|
||||
if(QUALITY_RESIZE)
|
||||
rawImage->resize(THUMB_SIZE, THUMB_SIZE);
|
||||
|
||||
rawImage->convertToGLFormat();
|
||||
rawImage->convertToThumbnail();
|
||||
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage));
|
||||
}
|
||||
QMetaObject::invokeMethod(m_receiver, "thumbnailLoadFinish", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage));
|
||||
}
|
||||
else
|
||||
{
|
||||
QMetaObject::invokeMethod(m_receiver, "imageLoaded", Qt::QueuedConnection, Q_ARG(std::shared_ptr<RawImage>, rawImage), Q_ARG(ImageInfoData, info));
|
||||
}
|
||||
}
|
||||
catch(std::exception e)
|
||||
{
|
||||
qDebug() << 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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,7 +480,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)
|
||||
@@ -537,10 +491,52 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
|
||||
return true;
|
||||
}
|
||||
|
||||
ConvertRunable::ConvertRunable(const QString &in, const QString &out, const QString &format) :
|
||||
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar)
|
||||
{
|
||||
bool ret = false;
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
if(path.endsWith(".CR2", Qt::CaseInsensitive) || path.endsWith(".CR3", Qt::CaseInsensitive) || path.endsWith(".NEF", Qt::CaseInsensitive) || path.endsWith(".DNG", Qt::CaseInsensitive))
|
||||
{
|
||||
ret = loadRAW(path, info, rawImage);
|
||||
qDebug() << "LoadRAW" << timer.elapsed();
|
||||
}
|
||||
else if(path.endsWith(".FIT", Qt::CaseInsensitive) || path.endsWith(".FITS", Qt::CaseInsensitive) || path.endsWith(".FZ", Qt::CaseInsensitive) || path.endsWith(".FTS", Qt::CaseInsensitive))
|
||||
{
|
||||
ret = loadFITS(path, info, rawImage, planar);
|
||||
qDebug() << "LoadFITS" << timer.elapsed();
|
||||
}
|
||||
else if(path.endsWith(".XISF", Qt::CaseInsensitive))
|
||||
{
|
||||
ret = loadXISF(path, info, rawImage, planar);
|
||||
qDebug() << "LoadXISF" << timer.elapsed();
|
||||
}
|
||||
else
|
||||
{
|
||||
QImage img(path);
|
||||
|
||||
ExifData *exif = exif_data_new_from_file(path.toLocal8Bit().constData());
|
||||
info.info.append({QObject::tr("Width"), QString::number(img.width())});
|
||||
info.info.append({QObject::tr("Height"), QString::number(img.height())});
|
||||
if(exif)
|
||||
{
|
||||
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_ISO_SPEED_RATINGS);
|
||||
loadExifEntry(info, exif->ifd[EXIF_IFD_EXIF], EXIF_TAG_SHUTTER_SPEED_VALUE);
|
||||
exif_data_free(exif);
|
||||
}
|
||||
rawImage = std::make_shared<RawImage>(img);
|
||||
qDebug() << "LoadQImage" << timer.elapsed();
|
||||
ret = !img.isNull();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ConvertRunable::ConvertRunable(const QString &in, const QString &out, const QString &format, const ConvertParams ¶ms, QSemaphore *semaphore) :
|
||||
m_infile(in),
|
||||
m_outfile(out),
|
||||
m_format(format)
|
||||
m_format(format),
|
||||
m_params(params),
|
||||
m_semaphore(semaphore)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -587,10 +583,12 @@ void writeFITSImage(fitsfile *fw, std::shared_ptr<RawImage> rawimage, ImageInfoD
|
||||
fits_write_pix(fw, TFLOAT, firstpix, rawimage->size(), planes[i].data(), &status);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
for(const FITSRecord &record : imageinfo.fitsHeader)
|
||||
{
|
||||
if(skipKeys.contains(record.key))continue;
|
||||
if(skipKeys.contains(record.key) || record.xisf)continue;
|
||||
|
||||
bool isdouble;
|
||||
bool isint;
|
||||
@@ -616,16 +614,18 @@ void writeFITSImage(fitsfile *fw, std::shared_ptr<RawImage> rawimage, ImageInfoD
|
||||
|
||||
void ConvertRunable::run()
|
||||
{
|
||||
QSemaphoreReleaser release;
|
||||
if(m_semaphore)release = QSemaphoreReleaser(m_semaphore);
|
||||
|
||||
ImageInfoData imageinfo;
|
||||
std::shared_ptr<RawImage> rawimage;
|
||||
if(m_infile.endsWith(".FITS", Qt::CaseInsensitive) || m_infile.endsWith(".FIT", Qt::CaseInsensitive))
|
||||
loadFITS(m_infile, imageinfo, rawimage);
|
||||
if(m_infile.endsWith(".XISF", Qt::CaseInsensitive))
|
||||
loadXISF(m_infile, imageinfo, rawimage);
|
||||
loadImage(m_infile, imageinfo, rawimage);
|
||||
QFileInfo info(m_outfile);
|
||||
info.dir().mkpath(".");
|
||||
|
||||
if(rawimage)
|
||||
{
|
||||
if(m_format == "XISF")
|
||||
if(m_format == "xisf")
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -640,16 +640,43 @@ void ConvertRunable::run()
|
||||
default: return;
|
||||
}
|
||||
|
||||
LibXISF::Image image(rawimage->width(), rawimage->height(), channelCount, sampleFormat, channelCount == 1 ? LibXISF::Image::Gray : LibXISF::Image::RGB, LibXISF::Image::Normal);
|
||||
std::memcpy(image.imageData(), rawimage->data(), image.imageDataSize());
|
||||
LibXISF::Image image(rawimage->width(), rawimage->height(), channelCount, sampleFormat, channelCount == 1 ? LibXISF::Image::Gray : LibXISF::Image::RGB, LibXISF::Image::Planar);
|
||||
if(channelCount == 1)
|
||||
{
|
||||
std::memcpy(image.imageData(), rawimage->data(), image.imageDataSize());
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t off = 0;
|
||||
std::vector<RawImage> planes = rawimage->split();
|
||||
for(const auto &plane : planes)
|
||||
{
|
||||
std::memcpy(image.imageData<uint8_t>() + off, plane.data(), plane.size() * RawImage::typeSize(plane.type()));
|
||||
off += plane.size() * RawImage::typeSize(plane.type());
|
||||
}
|
||||
}
|
||||
for(auto &record : imageinfo.fitsHeader)
|
||||
{
|
||||
if(record.value.type() == QVariant::Bool)
|
||||
if(record.xisf)continue;
|
||||
|
||||
if(record.value.typeId() == QMetaType::Bool)
|
||||
image.addFITSKeyword({record.key.toStdString(), record.value.toBool() ? "T" : "F", record.comment.toStdString()});
|
||||
else
|
||||
image.addFITSKeyword({record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
|
||||
}
|
||||
|
||||
if(m_params.compressionType.startsWith("zstd") && LibXISF::DataBlock::CompressionCodecSupported(LibXISF::DataBlock::ZSTD))
|
||||
image.setCompression(LibXISF::DataBlock::ZSTD, m_params.compressionLevel);
|
||||
else if(m_params.compressionType.startsWith("lz4hc"))
|
||||
image.setCompression(LibXISF::DataBlock::LZ4HC, m_params.compressionLevel);
|
||||
else if(m_params.compressionType.startsWith("lz4"))
|
||||
image.setCompression(LibXISF::DataBlock::LZ4, m_params.compressionLevel);
|
||||
else if(m_params.compressionType.startsWith("zlib"))
|
||||
image.setCompression(LibXISF::DataBlock::Zlib, m_params.compressionLevel);
|
||||
|
||||
if(m_params.compressionType.endsWith("+sh"))
|
||||
image.setByteshuffling(true);
|
||||
|
||||
xisf.writeImage(image);
|
||||
xisf.save(m_outfile.toLocal8Bit().data());
|
||||
}
|
||||
@@ -657,16 +684,75 @@ void ConvertRunable::run()
|
||||
{
|
||||
qDebug() << "Failed to save XISF image" << err.what();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_format == "FITS")
|
||||
if(m_format == "fits")
|
||||
{
|
||||
int status = 0;
|
||||
fitsfile *fw;
|
||||
if(QFileInfo(m_outfile).exists())QFile::remove(m_outfile);
|
||||
fits_create_diskfile(&fw, m_outfile.toLocal8Bit().data(), &status);
|
||||
if(!m_params.compressionType.isEmpty())
|
||||
{
|
||||
if(m_params.compressionType == "gzip")
|
||||
fits_set_compression_type(fw, GZIP_1, &status);
|
||||
else if(m_params.compressionType == "rice")
|
||||
fits_set_compression_type(fw, RICE_1, &status);
|
||||
}
|
||||
writeFITSImage(fw, rawimage, imageinfo);
|
||||
fits_close_file(fw, &status);
|
||||
return;
|
||||
}
|
||||
|
||||
// if nothing else try QImage
|
||||
{
|
||||
QImage::Format format = QImage::Format_Invalid;
|
||||
int width = rawimage->widthBytes();
|
||||
|
||||
switch(rawimage->type())
|
||||
{
|
||||
case RawImage::UINT8:
|
||||
if(rawimage->channels() == 1)format = QImage::Format_Grayscale8;
|
||||
else if(rawimage->channels() == 3)format = QImage::Format_RGBX8888;
|
||||
else if(rawimage->channels() == 4)format = QImage::Format_RGBA8888;
|
||||
break;
|
||||
case RawImage::UINT16:
|
||||
if(rawimage->channels() == 1)format = QImage::Format_Grayscale16;
|
||||
else if(rawimage->channels() == 3)format = QImage::Format_RGBX64;
|
||||
else if(rawimage->channels() == 4)format = QImage::Format_RGBA64;
|
||||
width *= 2;
|
||||
break;
|
||||
case RawImage::FLOAT16:
|
||||
case RawImage::FLOAT32:
|
||||
case RawImage::FLOAT64:
|
||||
case RawImage::UINT32:
|
||||
rawimage->convertToType(RawImage::UINT16);
|
||||
if(rawimage->channels() == 1)format = QImage::Format_Grayscale16;
|
||||
else if(rawimage->channels() == 3)format = QImage::Format_RGBX64;
|
||||
else if(rawimage->channels() == 4)format = QImage::Format_RGBA64;
|
||||
width *= 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if(format == QImage::Format_Invalid)return;
|
||||
|
||||
QImage qimage(rawimage->width(), rawimage->height(), format);
|
||||
for(uint32_t i=0; i < rawimage->height(); i++)
|
||||
std::memcpy(qimage.scanLine(i), rawimage->data(i), width);
|
||||
qimage.save(m_outfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConvertRunable::ConvertParams::ConvertParams(const QVariantMap &map)
|
||||
{
|
||||
bool ok = false;
|
||||
if(map.contains("compressionLevel"))
|
||||
compressionLevel = std::clamp(map["compressionLevel"].toInt(&ok), -1, 100);
|
||||
|
||||
if(!ok)compressionLevel = -1;
|
||||
|
||||
if(map.contains("compressionType"))
|
||||
compressionType = map["compressionType"].toString();
|
||||
}
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
|
||||
#include <QRunnable>
|
||||
#include <QString>
|
||||
#include <QSemaphore>
|
||||
#include "imageinfo.h"
|
||||
|
||||
class RawImage;
|
||||
|
||||
bool readFITSHeader(const QString &path, ImageInfoData &info);
|
||||
bool readXISFHeader(const QString &path, ImageInfoData &info);
|
||||
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar = false);
|
||||
|
||||
class Image;
|
||||
|
||||
@@ -21,14 +25,25 @@ public:
|
||||
void run() override;
|
||||
};
|
||||
|
||||
|
||||
class ConvertRunable : public QRunnable
|
||||
{
|
||||
public:
|
||||
struct ConvertParams
|
||||
{
|
||||
int compressionLevel = -1;
|
||||
QString compressionType;
|
||||
ConvertParams(){}
|
||||
ConvertParams(const QVariantMap &map);
|
||||
};
|
||||
ConvertRunable(const QString &in, const QString &out, const QString &format, const ConvertParams ¶ms = ConvertParams(), QSemaphore *semaphore = nullptr);
|
||||
void run() override;
|
||||
private:
|
||||
QString m_infile;
|
||||
QString m_outfile;
|
||||
QString m_format;
|
||||
public:
|
||||
ConvertRunable(const QString &in, const QString &out, const QString &format);
|
||||
void run() override;
|
||||
ConvertParams m_params;
|
||||
QSemaphore *m_semaphore;
|
||||
};
|
||||
|
||||
#endif // LOADRUNABLE_H
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <QProgressDialog>
|
||||
#include <QDebug>
|
||||
#include <QDockWidget>
|
||||
#include <QActionGroup>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <QSettings>
|
||||
@@ -18,12 +19,16 @@
|
||||
#include <QStatusBar>
|
||||
#include <QImageReader>
|
||||
#include <QMimeDatabase>
|
||||
#include <QDesktopServices>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include "loadrunable.h"
|
||||
#include "markedfiles.h"
|
||||
#include "about.h"
|
||||
#include "statusbar.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "histogram.h"
|
||||
#include "batchprocessing.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/ioctl.h>
|
||||
@@ -57,35 +62,36 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
_openFilter.append(" ");
|
||||
nameFilter.append(mimeType.suffixes());
|
||||
}
|
||||
_openFilter.append("*.fit *.fits *.xisf *.cr2 *.nef *.dng)");
|
||||
_openFilter.append("*.fit *.fits *.fts *.fz *.xisf *.cr2 *.cr3 *.nef *.dng)");
|
||||
_openFilter.append(tr(";;All files (*)"));
|
||||
nameFilter.append({"fit", "fits", "xisf", "cr2", "nef", "dng"});
|
||||
nameFilter.append({"fit", "fits", "fts", "fz", "xisf", "cr2", "cr3", "nef", "dng"});
|
||||
QImageReader::setAllocationLimit(0);
|
||||
|
||||
m_info = new ImageInfo(this);
|
||||
QDockWidget *infoDock = new QDockWidget(tr("Image info"), this);
|
||||
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);
|
||||
@@ -102,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);
|
||||
@@ -130,26 +142,35 @@ 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"), this, SLOT(loadFile()), QKeySequence::Open);
|
||||
fileMenu->addAction(tr("Save as"), this, SLOT(saveAs()), QKeySequence::Save);
|
||||
fileMenu->addAction(tr("Open"), QKeySequence::Open, this, SLOT(loadFile()));
|
||||
fileMenu->addAction(tr("Open directory recursively"), this, &MainWindow::loadDir);
|
||||
QAction *saveAs = fileMenu->addAction(tr("Save as"), QKeySequence::Save, this, SLOT(saveAs()));
|
||||
fileMenu->addSeparator();
|
||||
fileMenu->addAction(tr("Copy marked files"), this, SLOT(copyMarked()), Qt::Key_F5);
|
||||
fileMenu->addAction(tr("Move marked files"), this, SLOT(moveMarked()), Qt::Key_F6);
|
||||
fileMenu->addAction(tr("Move marked files to trash"), this, &MainWindow::deleteMarked, QKeySequence::Delete);
|
||||
fileMenu->addAction(tr("Copy marked files"), Qt::Key_F5, this, SLOT(copyMarked()));
|
||||
fileMenu->addAction(tr("Move marked files"), Qt::Key_F6, this, SLOT(moveMarked()));
|
||||
fileMenu->addAction(tr("Move marked files to trash"), QKeySequence::Delete, this, &MainWindow::deleteMarked);
|
||||
fileMenu->addSeparator();
|
||||
fileMenu->addAction(tr("Index directory"), this, SLOT(indexDir()));
|
||||
fileMenu->addAction(tr("Reindex files"), this, SLOT(reindex()));
|
||||
fileMenu->addAction(tr("Export database to CSV"), this, &MainWindow::exportCSV);
|
||||
fileMenu->addAction(tr("Batch processing"), Qt::Key_B | Qt::CTRL, [this](){
|
||||
BatchProcessing *batchProcessing = new BatchProcessing(this);
|
||||
batchProcessing->exec();
|
||||
delete batchProcessing;
|
||||
});
|
||||
fileMenu->addSeparator();
|
||||
QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, SLOT(liveMode(bool)));
|
||||
liveModeAction->setCheckable(true);
|
||||
@@ -162,30 +183,55 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
menuBar()->addMenu(editMenu);
|
||||
|
||||
QMenu *viewMenu = new QMenu(tr("View"), this);
|
||||
viewMenu->addAction(tr("Zoom In"), m_imageGL, SLOT(zoomIn()), QKeySequence::ZoomIn);
|
||||
viewMenu->addAction(tr("Zoom Out"), m_imageGL, SLOT(zoomOut()), QKeySequence::ZoomOut);
|
||||
viewMenu->addAction(tr("Best Fit"), m_imageGL, SLOT(bestFit()), QKeySequence("Ctrl+1"));
|
||||
viewMenu->addAction(tr("100%"), m_imageGL, SLOT(oneToOne()));
|
||||
QAction *thumbnailsAction = viewMenu->addAction(tr("Thumbnails"), [this](bool checked){
|
||||
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);
|
||||
QAction *rggbAction = bayerActionGroup->addAction(tr("RGGB"));//0 0
|
||||
QAction *grbgAction = bayerActionGroup->addAction(tr("GRBG"));//1 0
|
||||
QAction *gbrgAction = bayerActionGroup->addAction(tr("GBRG"));//0 1
|
||||
QAction *bggrAction = bayerActionGroup->addAction(tr("BGGR"));//1 1
|
||||
rggbAction->setCheckable(true); rggbAction->setData(0); rggbAction->setIcon(QIcon(":/bayer.png"));
|
||||
grbgAction->setCheckable(true); grbgAction->setData(1); grbgAction->setIcon(QIcon(":/grbg.png"));
|
||||
gbrgAction->setCheckable(true); gbrgAction->setData(2); gbrgAction->setIcon(QIcon(":/gbrg.png"));
|
||||
bggrAction->setCheckable(true); bggrAction->setData(3); bggrAction->setIcon(QIcon(":/bggr.png"));
|
||||
bayerMenu->addActions({rggbAction, grbgAction, gbrgAction, bggrAction});
|
||||
viewMenu->addMenu(bayerMenu);
|
||||
connect(bayerActionGroup, &QActionGroup::triggered, [this](QAction *action){
|
||||
int data = action->data().toInt();
|
||||
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();
|
||||
}, Qt::Key_F2);
|
||||
});
|
||||
thumbnailsAction->setCheckable(true);
|
||||
QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), Qt::Key_F3, m_ringList, &ImageRingList::toggleSlideshow);
|
||||
slideshowAction->setCheckable(true);
|
||||
menuBar()->addMenu(viewMenu);
|
||||
|
||||
QMenu *selectMenu = new QMenu(tr("Select"), this);
|
||||
selectMenu->addAction(tr("Mark"), this, SLOT(markImage()), Qt::Key_F7);
|
||||
selectMenu->addAction(tr("Unmark"), this, SLOT(unmarkImage()), Qt::Key_F8);
|
||||
selectMenu->addAction(tr("Mark"), Qt::Key_F7, this, SLOT(markImage()));
|
||||
selectMenu->addAction(tr("Unmark"), Qt::Key_F8, this, SLOT(unmarkImage()));
|
||||
selectMenu->addSeparator();
|
||||
selectMenu->addAction(tr("Mark and next"), this, SLOT(markAndNext()), Qt::Key_M);
|
||||
selectMenu->addAction(tr("Unmark and next"), this, SLOT(unmarkAndNext()), Qt::Key_X);
|
||||
selectMenu->addAction(tr("Show marked"), this, &MainWindow::showMarkFilesDialog);
|
||||
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->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);
|
||||
/*QMenu *analyzeMenu = new QMenu(tr("Analyze"), this);
|
||||
QActionGroup *analyzeGroup = new QActionGroup(this);
|
||||
connect(analyzeGroup, &QActionGroup::triggered, [](QAction* action) {
|
||||
static QAction* lastAction = nullptr;
|
||||
@@ -208,7 +254,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
connect(peakAction, SIGNAL(toggled(bool)), this, SLOT(peakFinder(bool)));
|
||||
connect(starAction, SIGNAL(toggled(bool)), this, SLOT(starFinder(bool)));
|
||||
analyzeMenu->addActions({statsAction, peakAction, starAction});
|
||||
//menuBar()->addMenu(analyzeMenu);
|
||||
menuBar()->addMenu(analyzeMenu);*/
|
||||
|
||||
QMenu *dockMenu = new QMenu(tr("Docks"), this);
|
||||
dockMenu->addAction(infoDock->toggleViewAction());
|
||||
@@ -217,17 +263,34 @@ 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"), [this]{ HelpDialog help(this); help.exec(); }, QKeySequence::HelpContents);
|
||||
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;
|
||||
restoreGeometry(settings.value("mainwindow/geometry").toByteArray());
|
||||
restoreState(settings.value("mainwindow/state").toByteArray());
|
||||
int bayermask = settings.value("mainwindow/bayermask", 0).toInt();
|
||||
switch(bayermask)
|
||||
{
|
||||
default:
|
||||
case 0:
|
||||
rggbAction->setChecked(true); break;
|
||||
case 1:
|
||||
grbgAction->setChecked(true); break;
|
||||
case 2:
|
||||
gbrgAction->setChecked(true); break;
|
||||
case 3:
|
||||
bggrAction->setChecked(true); break;
|
||||
}
|
||||
|
||||
QStringList standardLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
|
||||
if(standardLocations.size())
|
||||
@@ -239,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());
|
||||
@@ -250,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")
|
||||
@@ -343,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);
|
||||
@@ -354,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;
|
||||
@@ -394,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,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,
|
||||
@@ -444,6 +539,20 @@ void MainWindow::loadFile(int row)
|
||||
m_ringList->loadFile(row);
|
||||
}
|
||||
|
||||
void MainWindow::loadDir()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(this,
|
||||
tr("Open directory recursively"),
|
||||
_lastDir);
|
||||
if(!dir.isEmpty())
|
||||
{
|
||||
_lastDir = dir;
|
||||
m_ringList->setDir(dir, QString(), true);
|
||||
QSettings settings;
|
||||
settings.setValue("mainwindow/lastdir", _lastDir);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::indexDir()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Index directory"), _lastDir, QFileDialog::ShowDirsOnly);
|
||||
@@ -478,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));
|
||||
}
|
||||
@@ -641,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,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,10 +44,10 @@ protected:
|
||||
protected slots:
|
||||
void socketNotify();
|
||||
void updateWindowTitle();
|
||||
void pixmapLoaded(Image *image);
|
||||
void loadFile();
|
||||
void loadFile(const QString &path);
|
||||
void loadFile(int row);
|
||||
void loadDir();
|
||||
void indexDir();
|
||||
void indexDir(const QString &dir);
|
||||
void reindex();
|
||||
@@ -67,6 +67,7 @@ protected slots:
|
||||
void showMarkFilesDialog();
|
||||
void showSettingsDialog();
|
||||
void exportCSV();
|
||||
void checkNewVersion();
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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><html><head/><body><p>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 <span style=" font-weight:700;">any</span> index files (not only listed bellow) from astrometry.net by adding path pointing to them. <a href="https://astrometrynet.readthedocs.io/en/latest/readme.html#getting-index-files"><span style=" text-decoration: underline; color:#0000ff;">More details about index files.</span></a></p><p>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'.</p></body></html></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>
|
||||
@@ -2,14 +2,23 @@
|
||||
#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>
|
||||
void fromPlanarSSE(const void *in, void *out, size_t count);
|
||||
#endif
|
||||
|
||||
size_t RawImage::typeSize(RawImage::DataType type)
|
||||
{
|
||||
@@ -18,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:
|
||||
@@ -71,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);
|
||||
@@ -117,7 +128,7 @@ RawImage::RawImage(const QImage &img)
|
||||
m_stats.m_stats = false;
|
||||
}
|
||||
|
||||
const RawImage::Stats& RawImage::imageStats()
|
||||
const RawImage::Stats& RawImage::imageStats() const
|
||||
{
|
||||
return m_stats;
|
||||
}
|
||||
@@ -268,46 +279,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;
|
||||
@@ -335,7 +306,7 @@ RawImage::DataType RawImage::type() const
|
||||
|
||||
uint32_t RawImage::norm() const
|
||||
{
|
||||
switch(m_type)
|
||||
switch(m_origType)
|
||||
{
|
||||
case UINT8:
|
||||
return UINT8_MAX;
|
||||
@@ -348,6 +319,11 @@ uint32_t RawImage::norm() const
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t RawImage::widthBytes() const
|
||||
{
|
||||
return m_ch * m_width;
|
||||
}
|
||||
|
||||
void* RawImage::data()
|
||||
{
|
||||
return m_pixels.get();
|
||||
@@ -379,18 +355,31 @@ 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 = col * m_origWidth / m_width;
|
||||
row = row * m_origHeight / m_height;
|
||||
return m_original.get() + (m_origWidth * row * m_ch + col * m_ch) * typeSize(m_origType);
|
||||
}
|
||||
else
|
||||
return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type);
|
||||
}
|
||||
|
||||
bool RawImage::planar() const
|
||||
{
|
||||
return m_planar;
|
||||
}
|
||||
|
||||
void RawImage::convertToThumbnail()
|
||||
{
|
||||
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());
|
||||
if(!valid())
|
||||
return;
|
||||
|
||||
auto loop = [&](uint16_t *out, auto *in, auto scale)
|
||||
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(F16));
|
||||
F16 *out = reinterpret_cast<F16*>(outptr.get());
|
||||
|
||||
auto loop = [&](F16 *out, auto *in, float scale)
|
||||
{
|
||||
for(int i=0; i<THUMB_SIZE; i++)
|
||||
{
|
||||
@@ -398,17 +387,18 @@ void RawImage::convertToThumbnail()
|
||||
{
|
||||
int idx = (i*THUMB_SIZE + o)*4;
|
||||
int 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -416,16 +406,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";
|
||||
@@ -437,34 +430,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(uint32_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(uint32_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(uint32_t i = 0; i < size; i++)
|
||||
dst[i] = src[i] >> ((sizeof(T) - sizeof(U)) * 8);
|
||||
else
|
||||
for(uint32_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(uint32_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(uint32_t i = 0; i < size; i++)
|
||||
dst[i] = (U)src[i] * scale;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void convertType(uint32_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;
|
||||
|
||||
uint32_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
|
||||
@@ -553,18 +624,134 @@ 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;
|
||||
}
|
||||
|
||||
void RawImage::downscaleTo(uint32_t size)
|
||||
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(size < width() || size < height())
|
||||
if(oldw == 0 || oldh == 0)return;
|
||||
|
||||
const T *in = reinterpret_cast<const T*>(in_);
|
||||
T *out = reinterpret_cast<T*>(out_);
|
||||
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
|
||||
{
|
||||
double s = (double)size / std::max(width(), height());
|
||||
cv::Size dsize(std::floor(width() * s), std::floor(height() * s));
|
||||
cv::resize(m_img, m_img, dsize, 0, 0, cv::INTER_AREA);
|
||||
}*/
|
||||
for(uint32_t x = 0; x < w; x++)//iterate over destination X
|
||||
{
|
||||
U 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);
|
||||
for(uint32_t o = yy; o <= ye; o++)//iterate over source Y
|
||||
{
|
||||
float cy = o * sy - y;
|
||||
if(cy < 0.0f)cy = sy + cy;
|
||||
else if(sy + cy > 1.0f)cy = 1.0f - cy;
|
||||
else cy = sy;
|
||||
if(yy==ye)cy = 1.0f;
|
||||
for(uint32_t i = xx; i <= xe; i++)//iterate over source X
|
||||
{
|
||||
float cx = i * sx - x;
|
||||
if(cx < 0.0f)cx = sx + cx;
|
||||
else if(sx + cx > 1.0f)cx = 1.0f - cx;
|
||||
else cx = sx;
|
||||
if(xx==xe)cx = 1.0f;
|
||||
for(uint32_t z = 0; z < ch; z++)
|
||||
p[z] += in[(o * oldw + i) * ch + z] * cy * cx;
|
||||
}
|
||||
}
|
||||
for(uint32_t z = 0; z < ch; 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RawImage::resize(uint32_t w, uint32_t h)
|
||||
{
|
||||
if(!valid())return;
|
||||
|
||||
std::unique_ptr<PixelType[]> old_pixels = std::move(m_pixels);
|
||||
uint32_t oldw = m_width;
|
||||
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)
|
||||
{
|
||||
case RawImage::UINT8:
|
||||
boxResample<uint8_t>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
|
||||
break;
|
||||
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;
|
||||
case RawImage::FLOAT64:
|
||||
boxResample<double, double>(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<float, float> RawImage::unitScale() const
|
||||
{
|
||||
float min = *std::min_element(m_stats.m_min, m_stats.m_min + 4);
|
||||
float max = *std::max_element(m_stats.m_max, m_stats.m_max + 4);
|
||||
|
||||
if(m_origType == UINT32)
|
||||
{
|
||||
min /= (float)UINT32_MAX;
|
||||
max /= (float)UINT32_MAX;
|
||||
}
|
||||
|
||||
if(min < 0.0f || max > 1.0f)
|
||||
return {1.0f / (max - min), min / (max - min)};
|
||||
else
|
||||
return {1.0f, 0.0f};
|
||||
}
|
||||
|
||||
void RawImage::flip()
|
||||
{
|
||||
std::unique_ptr<PixelType[]> tmp = std::move(m_pixels);
|
||||
allocate(m_width, m_height, m_channels, m_type);
|
||||
uint32_t rowSize = m_width * m_ch * typeSize(m_type);
|
||||
for(uint32_t i=0; i<m_height; i++)
|
||||
std::memcpy(m_pixels.get() + rowSize * (m_height - i - 1), tmp.get() + rowSize * i, rowSize);
|
||||
}
|
||||
|
||||
std::shared_ptr<RawImage> RawImage::fromPlanar(const RawImage &img)
|
||||
@@ -610,6 +797,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)
|
||||
@@ -634,9 +824,43 @@ std::shared_ptr<RawImage> RawImage::fromPlanar(const void *pixels, uint32_t w, u
|
||||
convert(static_cast<const double*>(pixels), static_cast<double*>(image->data()), 1);
|
||||
break;
|
||||
}
|
||||
image->m_planar = false;
|
||||
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;
|
||||
@@ -647,17 +871,18 @@ std::vector<RawImage> RawImage::split() const
|
||||
size_t s = size();
|
||||
auto extract = [&](auto *in, auto *out, size_t off)
|
||||
{
|
||||
for(size_t i=0; i < s; i+=m_ch)
|
||||
for(size_t i=0; i < s; i++)
|
||||
out[i] = in[i*m_ch + off];
|
||||
};
|
||||
|
||||
for(uint32_t i=0; i<m_ch; i++)
|
||||
for(uint32_t i=0; i<m_channels; i++)
|
||||
{
|
||||
switch(m_type)
|
||||
{
|
||||
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;
|
||||
@@ -673,3 +898,127 @@ std::vector<RawImage> RawImage::split() const
|
||||
|
||||
return planes;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -12,6 +12,7 @@
|
||||
extern int THUMB_SIZE;
|
||||
extern int THUMB_SIZE_BORDER;
|
||||
extern int THUMB_SIZE_BORDER_Y;
|
||||
extern bool QUALITY_RESIZE;
|
||||
|
||||
class Peak
|
||||
{
|
||||
@@ -44,6 +45,7 @@ public:
|
||||
UINT8,
|
||||
UINT16,
|
||||
UINT32,
|
||||
FLOAT16,
|
||||
FLOAT32,
|
||||
FLOAT64,
|
||||
};
|
||||
@@ -64,12 +66,17 @@ 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;
|
||||
DataType m_origType = UINT8;
|
||||
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();
|
||||
@@ -77,32 +84,44 @@ public:
|
||||
RawImage(const RawImage &d);
|
||||
RawImage(RawImage &&d);
|
||||
RawImage(const QImage &img);
|
||||
const RawImage::Stats& imageStats();
|
||||
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;
|
||||
DataType type() const;
|
||||
uint32_t norm() const;
|
||||
uint32_t widthBytes() const;
|
||||
void* data();
|
||||
const void* data() const;
|
||||
void* data(uint32_t row, uint32_t col = 0);
|
||||
const void* data(uint32_t row, uint32_t col = 0) const;
|
||||
const void *origData() const;
|
||||
const void *origData(uint32_t row, uint32_t col = 0) const;
|
||||
bool planar() const;
|
||||
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 downscaleTo(uint32_t size);
|
||||
void resize(uint32_t w, uint32_t h);
|
||||
std::pair<float, float> unitScale() const;
|
||||
void flip();
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "rawimage.h"
|
||||
|
||||
#ifdef __SSE2__
|
||||
#include <x86intrin.h>
|
||||
|
||||
template<typename T, int ch>
|
||||
@@ -19,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);
|
||||
@@ -41,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);
|
||||
@@ -64,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);
|
||||
@@ -82,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;
|
||||
@@ -109,3 +111,5 @@ template void fromPlanarSSE<uint32_t, 3>(const void *in, void *out, size_t count
|
||||
template void fromPlanarSSE<uint32_t, 4>(const void *in, void *out, size_t count);
|
||||
template void fromPlanarSSE<float, 3>(const void *in, void *out, size_t count);
|
||||
template void fromPlanarSSE<float, 4>(const void *in, void *out, size_t count);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<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>
|
||||
<file>translations/tenmon_sk.qm</file>
|
||||
<file>about/filter.png</file>
|
||||
<file>about/stretch-panel.png</file>
|
||||
<file>translations/tenmon_fr.qm</file>
|
||||
<file>shaders/image.frag</file>
|
||||
<file>shaders/image.vert</file>
|
||||
<file>shaders/thumb.frag</file>
|
||||
<file>shaders/thumb.vert</file>
|
||||
<file>shaders/debayer.frag</file>
|
||||
<file>shaders/debayer.vert</file>
|
||||
<file>falsecolor.png</file>
|
||||
<file>link.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="/" lang="en">
|
||||
<file alias="help">about/help_en</file>
|
||||
</qresource>
|
||||
<qresource prefix="/" lang="sk">
|
||||
<file alias="help">about/help_sk</file>
|
||||
</qresource>
|
||||
<qresource prefix="/" lang="fr">
|
||||
<file alias="help">about/help_fr</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
After Width: | Height: | Size: 122 B |
|
After Width: | Height: | Size: 316 B |
|
Before Width: | Height: | Size: 947 B After Width: | Height: | Size: 947 B |
|
After Width: | Height: | Size: 316 B |
|
After Width: | Height: | Size: 316 B |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 454 B After Width: | Height: | Size: 454 B |
@@ -0,0 +1,29 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>invert.png</file>
|
||||
<file>nuke.png</file>
|
||||
<file>bayer.png</file>
|
||||
<file>nuke_a.png</file>
|
||||
<file>../about/tenmon</file>
|
||||
<file>../translations/tenmon_en.qm</file>
|
||||
<file>../translations/tenmon_sk.qm</file>
|
||||
<file>../about/filter.png</file>
|
||||
<file>../about/stretch-panel.png</file>
|
||||
<file>../translations/tenmon_fr.qm</file>
|
||||
<file>falsecolor.png</file>
|
||||
<file>link.png</file>
|
||||
<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>
|
||||
</qresource>
|
||||
<qresource lang="sk" prefix="/">
|
||||
<file alias="help">../about/help_sk</file>
|
||||
</qresource>
|
||||
<qresource lang="fr" prefix="/">
|
||||
<file alias="help">../about/help_fr</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,762 @@
|
||||
#include "scriptengine.h"
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
#include <QInputDialog>
|
||||
#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
|
||||
{
|
||||
|
||||
ScriptEngine::ScriptEngine(BatchProcessing *parent)
|
||||
: _jsEngine(new QJSEngine(this))
|
||||
, _database(new Database(this))
|
||||
, _parent(parent)
|
||||
, _pool(new QThreadPool(this))
|
||||
{
|
||||
QJSValue core = _jsEngine->newQObject(this);
|
||||
_jsEngine->globalObject().setProperty("core", core);
|
||||
QJSValue fitsRecordObject = _jsEngine->newQMetaObject(&FITSRecordModify::staticMetaObject);
|
||||
_jsEngine->globalObject().setProperty("FITSRecordModify", fitsRecordObject);
|
||||
_database->init(QLatin1String("scriptengine"));
|
||||
_semaphore.release(_pool->maxThreadCount());
|
||||
|
||||
#ifdef PLATESOLVER
|
||||
_solver = new Solver(this);
|
||||
#endif // PLATESOLVER
|
||||
}
|
||||
|
||||
void ScriptEngine::setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir)
|
||||
{
|
||||
_scriptPath = scriptPath;
|
||||
_paths = paths;
|
||||
_outputDir = outputDir + "/";
|
||||
}
|
||||
|
||||
void ScriptEngine::reportError(const QString &message)
|
||||
{
|
||||
_jsEngine->throwError(message);
|
||||
}
|
||||
|
||||
const QString &ScriptEngine::outputDir() const
|
||||
{
|
||||
return _outputDir;
|
||||
}
|
||||
|
||||
void ScriptEngine::interrupt()
|
||||
{
|
||||
#ifdef PLATESOLVER
|
||||
if(_solver)_solver->abort();
|
||||
#endif
|
||||
_jsEngine->setInterrupted(true);
|
||||
}
|
||||
|
||||
void ScriptEngine::logError(const QString &message)
|
||||
{
|
||||
emit newMessage(message, true);
|
||||
}
|
||||
|
||||
void ScriptEngine::log(const QString &message)
|
||||
{
|
||||
emit newMessage(message, false);
|
||||
}
|
||||
|
||||
void ScriptEngine::mark(File *file)
|
||||
{
|
||||
_database->mark(file->absoluteFilePath());
|
||||
}
|
||||
|
||||
void ScriptEngine::unmark(File *file)
|
||||
{
|
||||
_database->unmark(file->absoluteFilePath());
|
||||
}
|
||||
|
||||
bool ScriptEngine::isMarked(const File *file) const
|
||||
{
|
||||
return _database->isMarked(file->absoluteFilePath());
|
||||
}
|
||||
|
||||
void ScriptEngine::setMaxThread(int maxthread)
|
||||
{
|
||||
int newval = std::max(std::min(QThread::idealThreadCount(), maxthread), 1);
|
||||
int oldval = _pool->maxThreadCount();
|
||||
if(newval > oldval)
|
||||
_semaphore.release(newval - oldval);
|
||||
else if(newval < oldval)
|
||||
_semaphore.acquire(oldval - newval);
|
||||
_pool->setMaxThreadCount(newval);
|
||||
}
|
||||
|
||||
void ScriptEngine::sync()
|
||||
{
|
||||
_pool->waitForDone();
|
||||
}
|
||||
|
||||
QJSValue ScriptEngine::getString(const QString &label, const QString &text) const
|
||||
{
|
||||
QJSValue ret;
|
||||
QMetaObject::invokeMethod(_parent, "getString", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QJSValue, ret), Q_ARG(QString, label), Q_ARG(QString, text));
|
||||
return ret;
|
||||
}
|
||||
|
||||
QJSValue ScriptEngine::getInt(const QString &label, int value)
|
||||
{
|
||||
QJSValue ret;
|
||||
QMetaObject::invokeMethod(_parent, "getInt", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QJSValue, ret), Q_ARG(QString, label), Q_ARG(int, value));
|
||||
return ret;
|
||||
}
|
||||
|
||||
QJSValue ScriptEngine::getFloat(const QString &label, double value, int decimals) const
|
||||
{
|
||||
QJSValue ret;
|
||||
QMetaObject::invokeMethod(_parent, "getFloat", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QJSValue, ret), Q_ARG(QString, label), Q_ARG(double, value), Q_ARG(int, decimals));
|
||||
return ret;
|
||||
}
|
||||
|
||||
QJSValue ScriptEngine::getItem(const QStringList &items, const QString &label, int current) const
|
||||
{
|
||||
QJSValue ret;
|
||||
QMetaObject::invokeMethod(_parent, "getItem", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QJSValue, ret), Q_ARG(QStringList, items), Q_ARG(QString, label), Q_ARG(int, current));
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ScriptEngine::convert(File *file, QString &outpath, const QString &format, const QVariantMap ¶ms, bool async)
|
||||
{
|
||||
QString path;
|
||||
QDir dir(_outputDir);
|
||||
QFileInfo info(outpath);
|
||||
QString suffix = info.suffix();
|
||||
if(info.isAbsolute())
|
||||
path = info.absolutePath();
|
||||
else
|
||||
path = dir.absoluteFilePath(outpath);
|
||||
|
||||
QString f = format.toLower();
|
||||
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 tiff");
|
||||
return false;
|
||||
}
|
||||
|
||||
info.setFile(path);
|
||||
outpath = info.absolutePath() + "/" + info.completeBaseName() + "." + f;
|
||||
if(async)
|
||||
{
|
||||
_semaphore.acquire();
|
||||
_pool->start(new ConvertRunable(file->absoluteFilePath(), outpath, f, params, &_semaphore));
|
||||
}
|
||||
else
|
||||
{
|
||||
ConvertRunable crun(file->absoluteFilePath(), outpath, f, params, nullptr);
|
||||
crun.run();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef PLATESOLVER
|
||||
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();
|
||||
}
|
||||
|
||||
QJSValue ScriptEngine::newArray(uint size)
|
||||
{
|
||||
return _jsEngine->newArray(size);
|
||||
}
|
||||
|
||||
void ScriptEngine::run()
|
||||
{
|
||||
QJSValue jsPaths = _jsEngine->newArray(_paths.size());
|
||||
for(qsizetype i=0; i<_paths.size(); i++)
|
||||
jsPaths.setProperty(i, _jsEngine->newQObject(new File(_paths[i].first, _paths[i].second, this)));
|
||||
|
||||
_jsEngine->globalObject().setProperty("files", jsPaths);
|
||||
|
||||
QFile scriptFile(_scriptPath);
|
||||
if(!scriptFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
emit newMessage("Failed to open " + _scriptPath, true);
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream stream(&scriptFile);
|
||||
QString contents = stream.readAll();
|
||||
scriptFile.close();
|
||||
QJSValue result = _jsEngine->evaluate(contents, _scriptPath);
|
||||
qDebug() << result.isError() << result.toString();
|
||||
_pool->waitForDone();
|
||||
if(result.isError())
|
||||
{
|
||||
QString error = result.property("name").toString() + " on line " + result.property("lineNumber").toString() + " : " + result.toString();
|
||||
error += "\n" + result.property("stack").toString();
|
||||
emit newMessage(error, true);
|
||||
}
|
||||
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void File::loadFitsKeywords()
|
||||
{
|
||||
if(!_fitsKeywordsLoaded)
|
||||
{
|
||||
_fitsKeywordsLoaded = true;
|
||||
ImageInfoData info;
|
||||
if(suffix().toLower() == "xisf")
|
||||
{
|
||||
readXISFHeader(_path, info);
|
||||
}
|
||||
else if(suffix().toLower() == "fits" || suffix().toLower() == "fit")
|
||||
{
|
||||
readFITSHeader(_path, info);
|
||||
}
|
||||
else return;
|
||||
|
||||
for(auto &record : info.fitsHeader)
|
||||
{
|
||||
_fitsKeywords.append(record.key);
|
||||
_fitsRecords.insert(record.key, record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool File::mkpath(const QString &path) const
|
||||
{
|
||||
QFileInfo info(path);
|
||||
if(!info.isRelative())
|
||||
{
|
||||
_engine->logError("Destination path is not relative");
|
||||
return false;
|
||||
}
|
||||
QDir dir(_engine->outputDir());
|
||||
if(dir.mkpath(info.path()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_engine->logError("Failed to create dir " + info.path());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
File::File(const QString &path, Script::ScriptEngine *engine) : File(path, QString(), engine)
|
||||
{
|
||||
}
|
||||
|
||||
File::File(const QString &path, const QString &root, ScriptEngine *engine) :
|
||||
_engine(engine),
|
||||
_path(path),
|
||||
_root(root),
|
||||
_info(path)
|
||||
{
|
||||
}
|
||||
|
||||
QString File::fileName() const
|
||||
{
|
||||
return _info.fileName();
|
||||
}
|
||||
|
||||
QString File::absoluteFilePath() const
|
||||
{
|
||||
return _info.absoluteFilePath();
|
||||
}
|
||||
|
||||
QString File::absolutePath() const
|
||||
{
|
||||
return _info.absolutePath();
|
||||
}
|
||||
|
||||
QString File::relativeFilePath() const
|
||||
{
|
||||
QDir dir(_root);
|
||||
return dir.relativeFilePath(_info.absoluteFilePath());
|
||||
}
|
||||
|
||||
QString File::relativePath() const
|
||||
{
|
||||
QDir dir(_root);
|
||||
return dir.relativeFilePath(_info.absolutePath());
|
||||
}
|
||||
|
||||
QString File::baseName() const
|
||||
{
|
||||
return _info.baseName();
|
||||
}
|
||||
|
||||
QString File::completeBaseName() const
|
||||
{
|
||||
return _info.completeBaseName();
|
||||
}
|
||||
|
||||
QString File::suffix() const
|
||||
{
|
||||
return _info.suffix();
|
||||
}
|
||||
|
||||
qint64 File::size() const
|
||||
{
|
||||
return _info.size();
|
||||
}
|
||||
|
||||
QStringList File::fitsKeywords()
|
||||
{
|
||||
loadFitsKeywords();
|
||||
return _fitsKeywords;
|
||||
}
|
||||
|
||||
QString File::fitsValue(const QString &key)
|
||||
{
|
||||
loadFitsKeywords();
|
||||
if(_fitsRecords.contains(key))
|
||||
return _fitsRecords[key].value.toString();
|
||||
else
|
||||
return QString();
|
||||
}
|
||||
|
||||
QJSValue File::fitsValues(const QString &key)
|
||||
{
|
||||
loadFitsKeywords();
|
||||
if(_fitsRecords.contains(key))
|
||||
{
|
||||
QList<FITSRecord> values = _fitsRecords.values(key);
|
||||
QJSValue array = _engine->newArray(values.size());
|
||||
for(qsizetype i=0; i<values.size(); i++)
|
||||
array.setProperty(i, values[i].value.toString());
|
||||
return array;
|
||||
}
|
||||
else
|
||||
return QString();
|
||||
}
|
||||
|
||||
QJSValue File::fitsRecords()
|
||||
{
|
||||
loadFitsKeywords();
|
||||
|
||||
QJSValue array = _engine->newArray(_fitsRecords.size());
|
||||
uint i = 0;
|
||||
for(auto &record : _fitsRecords)
|
||||
{
|
||||
QJSValue item = _engine->newObject();
|
||||
item.setProperty("key", QString::fromUtf8(record.key));
|
||||
item.setProperty("value", record.value.toString());
|
||||
item.setProperty("comment", QString::fromUtf8(record.comment));
|
||||
item.setProperty("xisf", record.xisf);
|
||||
array.setProperty(i++, item);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
bool File::modifyFITSRecords(const FITSRecordModify *modify)
|
||||
{
|
||||
_fitsKeywordsLoaded = false;
|
||||
_fitsKeywords.clear();
|
||||
|
||||
if(QRegularExpression("(fits?|fz|fts)", QRegularExpression::CaseInsensitiveOption).match(suffix()).hasMatch())
|
||||
{
|
||||
fitsfile *file;
|
||||
int status = 0;
|
||||
fits_open_diskfile(&file, _path.toLocal8Bit().data(), READWRITE, &status);
|
||||
int num = 0;
|
||||
fits_get_num_hdus(file, &num, &status);
|
||||
if(status)
|
||||
{
|
||||
if(_engine)_engine->newMessage("Failed to open FITS file", true);
|
||||
return false;
|
||||
}
|
||||
int imgtype;
|
||||
int naxis;
|
||||
long naxes[3] = {0};
|
||||
int type = -1;
|
||||
for(int i=1; i <= num; i++)
|
||||
{
|
||||
fits_movabs_hdu(file, i, IMAGE_HDU, &status);
|
||||
fits_get_hdu_type(file, &type, &status);
|
||||
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
|
||||
if(type == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
|
||||
break;
|
||||
if(i == num)return false;
|
||||
}
|
||||
|
||||
for(auto &remove : modify->_remove)
|
||||
{
|
||||
int status = 0;//we ignore errors from here
|
||||
fits_delete_key(file, remove.toLatin1().data(), &status);
|
||||
}
|
||||
for(auto &record : modify->_update)
|
||||
{
|
||||
switch(record.value.typeId())
|
||||
{
|
||||
case QMetaType::Bool:
|
||||
{
|
||||
int val = record.value.toBool();
|
||||
fits_update_key(file, TLOGICAL, record.key.data(), &val, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
break;
|
||||
}
|
||||
case QMetaType::Int:
|
||||
case QMetaType::UInt:
|
||||
{
|
||||
long long val = record.value.toLongLong();
|
||||
fits_update_key(file, TLONGLONG, record.key.data(), &val, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
break;
|
||||
}
|
||||
case QMetaType::QString:
|
||||
{
|
||||
QByteArray val = record.value.toString().toLatin1();
|
||||
fits_update_key(file, TSTRING, record.key.data(), val.isEmpty() ? nullptr : val.data(), record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
break;
|
||||
}
|
||||
case QMetaType::Float:
|
||||
case QMetaType::Double:
|
||||
{
|
||||
double val = record.value.toDouble();
|
||||
fits_update_key(file, TDOUBLE, record.key.data(), &val, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if(_engine)_engine->newMessage("Unknown type for KEY " + record.key, true);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
if(status)
|
||||
{
|
||||
char error[100];
|
||||
fits_get_errstatus(status, error);
|
||||
if(_engine)_engine->newMessage(QString("Error when updating KEY %1 %2").arg(record.key).arg(error), true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto &record : modify->_add)
|
||||
{
|
||||
switch(record.value.typeId())
|
||||
{
|
||||
case QMetaType::Bool:
|
||||
{
|
||||
int val = record.value.toBool();
|
||||
fits_write_key(file, TLOGICAL, record.key.data(), &val, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
break;
|
||||
}
|
||||
case QMetaType::Int:
|
||||
case QMetaType::UInt:
|
||||
{
|
||||
long long val = record.value.toLongLong();
|
||||
fits_write_key(file, TLONGLONG, record.key.data(), &val, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
break;
|
||||
}
|
||||
case QMetaType::QString:
|
||||
{
|
||||
QByteArray val = record.value.toString().toLatin1();
|
||||
fits_write_key(file, TSTRING, record.key.data(), val.isEmpty() ? nullptr : val.data(), record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
break;
|
||||
}
|
||||
case QMetaType::Float:
|
||||
case QMetaType::Double:
|
||||
{
|
||||
double val = record.value.toDouble();
|
||||
fits_write_key(file, TDOUBLE, record.key.data(), &val, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if(_engine)_engine->newMessage("Unknown type for KEY " + record.key, true);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
if(status)
|
||||
{
|
||||
char error[100];
|
||||
fits_get_errstatus(status, error);
|
||||
if(_engine)_engine->newMessage(QString("Error when adding KEY {} {}").arg(record.key).arg(error), true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fits_close_file(file, &status);
|
||||
|
||||
return status == 0;
|
||||
}
|
||||
else if(suffix().toLower() == "xisf")
|
||||
{
|
||||
try
|
||||
{
|
||||
LibXISF::XISFModify modifyXISF;
|
||||
modifyXISF.open(_path.toLocal8Bit().data());
|
||||
QFileInfo in(_path);
|
||||
QFileInfo out(_path + "~");
|
||||
|
||||
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.comment.toStdString()}, true);
|
||||
|
||||
for(auto &record : modify->_add)
|
||||
modifyXISF.addFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
|
||||
|
||||
modifyXISF.save(out.absoluteFilePath().toLocal8Bit().toStdString());
|
||||
modifyXISF.close();
|
||||
std::filesystem::rename(out.filesystemAbsoluteFilePath(), in.filesystemAbsoluteFilePath());
|
||||
return true;
|
||||
}
|
||||
catch(std::filesystem::filesystem_error &err)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch(LibXISF::Error &err)
|
||||
{
|
||||
if(_engine)_engine->newMessage("Failed to modify file " + _path + " " + err.what(), true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool File::isMarked() const
|
||||
{
|
||||
return _engine->isMarked(this);
|
||||
}
|
||||
|
||||
File* File::copy(const QString &newpath) const
|
||||
{
|
||||
if(mkpath(newpath))
|
||||
{
|
||||
if(QFile::copy(_path, _engine->outputDir() + newpath))
|
||||
return new File(_engine->outputDir() + newpath, _engine);
|
||||
_engine->logError("Failed copy to " + newpath);
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool File::move(const QString &newpath)
|
||||
{
|
||||
if(mkpath(newpath))
|
||||
{
|
||||
if(QFile::rename(_path, _engine->outputDir() + newpath))
|
||||
{
|
||||
_path = _engine->outputDir() + newpath;
|
||||
return true;
|
||||
}
|
||||
_engine->logError("Failed move to " + newpath);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
File* File::convert(const QString &outpath, const QString &format, const QVariantMap ¶ms)
|
||||
{
|
||||
QString path = outpath;
|
||||
if(_engine->convert(this, path, format, params, false))
|
||||
return new File(path, _engine);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
File* File::convertAsync(const QString &outpath, const QString &format, const QVariantMap ¶ms)
|
||||
{
|
||||
QString path = outpath;
|
||||
if(_engine->convert(this, path, format, params, true))
|
||||
return new File(path, _engine);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QJSValue File::stats()
|
||||
{
|
||||
if(_stats.isUndefined())
|
||||
{
|
||||
ImageInfoData info;
|
||||
std::shared_ptr<RawImage> rawImage;
|
||||
loadImage(_path, info, rawImage);
|
||||
rawImage->calcStats();
|
||||
RawImage::Stats stats = rawImage->imageStats();
|
||||
_stats = _engine->newObject();
|
||||
_stats.setProperty("mean", stats.m_mean[0]);
|
||||
_stats.setProperty("stddev", stats.m_stdDev[0]);
|
||||
_stats.setProperty("median", stats.m_median[0]);
|
||||
_stats.setProperty("min", stats.m_min[0]);
|
||||
_stats.setProperty("max", stats.m_max[0]);
|
||||
_stats.setProperty("mad", stats.m_mean[0]);
|
||||
}
|
||||
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();
|
||||
_thread->setObjectName("ScriptEngine");
|
||||
_engine = new ScriptEngine(parent);
|
||||
_engine->moveToThread(_thread);
|
||||
connect(_engine, &ScriptEngine::finished, _thread, &QThread::quit);
|
||||
connect(_engine, &ScriptEngine::newMessage, this, &ScriptEngineThread::newMessage);
|
||||
connect(_thread, &QThread::started, _engine, &ScriptEngine::run);
|
||||
connect(_thread, &QThread::finished, _engine, &ScriptEngine::deleteLater);
|
||||
connect(_engine, &ScriptEngine::destroyed, [this](){ _engine = nullptr; });
|
||||
connect(_thread, &QThread::finished, _thread, &QThread::deleteLater);
|
||||
connect(_thread, &QThread::finished, this, &ScriptEngineThread::finished);
|
||||
}
|
||||
|
||||
ScriptEngineThread::~ScriptEngineThread()
|
||||
{
|
||||
if(_engine)_engine->interrupt();
|
||||
}
|
||||
|
||||
void ScriptEngineThread::setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir)
|
||||
{
|
||||
_engine->setParams(scriptPath, paths, outputDir);
|
||||
}
|
||||
|
||||
void ScriptEngineThread::start()
|
||||
{
|
||||
_thread->start();
|
||||
}
|
||||
|
||||
void ScriptEngineThread::interrupt()
|
||||
{
|
||||
if(_engine)_engine->interrupt();
|
||||
}
|
||||
|
||||
void FITSRecordModify::removeKeyword(const QString &key)
|
||||
{
|
||||
if(!_remove.contains(key))
|
||||
_remove.append(key);
|
||||
}
|
||||
|
||||
void FITSRecordModify::updateKeyword(const QString &key, const QVariant &value, const QString &comment)
|
||||
{
|
||||
_update.append({key.toLatin1(), value, comment.toLatin1()});
|
||||
}
|
||||
|
||||
void FITSRecordModify::addKeyword(const QString &key, const QVariant &value, const QString &comment)
|
||||
{
|
||||
_update.append({key.toLatin1(), value, comment.toLatin1()});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
#ifndef SCRIPTENGINE_H
|
||||
#define SCRIPTENGINE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QJSEngine>
|
||||
#include <QFileInfo>
|
||||
#include <QThread>
|
||||
#include <QThreadPool>
|
||||
#include <QSemaphore>
|
||||
#include "database.h"
|
||||
#include "imageinfo.h"
|
||||
|
||||
class BatchProcessing;
|
||||
class Solver;
|
||||
|
||||
namespace Script
|
||||
{
|
||||
|
||||
class File;
|
||||
|
||||
class ScriptEngine : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QJSEngine *_jsEngine;
|
||||
Database *_database;
|
||||
BatchProcessing *_parent;
|
||||
QThreadPool *_pool;
|
||||
QSemaphore _semaphore;
|
||||
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);
|
||||
void reportError(const QString &message);
|
||||
const QString& outputDir() const;
|
||||
void interrupt();
|
||||
void logError(const QString &message);
|
||||
Q_INVOKABLE void log(const QString &message);
|
||||
Q_INVOKABLE void mark(File *file);
|
||||
Q_INVOKABLE void unmark(File *file);
|
||||
Q_INVOKABLE bool isMarked(const File *file) const;
|
||||
Q_INVOKABLE void setMaxThread(int maxthread);
|
||||
Q_INVOKABLE void sync();
|
||||
Q_INVOKABLE QJSValue getString(const QString &label = QString(), const QString &text = QString()) const;
|
||||
Q_INVOKABLE QJSValue getInt(const QString &label = QString(), int value = 0);
|
||||
Q_INVOKABLE QJSValue 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 ¶ms, bool async);
|
||||
#ifdef PLATESOLVER
|
||||
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:
|
||||
void run();
|
||||
signals:
|
||||
void newMessage(const QString &message, bool error);
|
||||
void finished();
|
||||
};
|
||||
|
||||
class ScriptEngineThread : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QThread *_thread;
|
||||
ScriptEngine *_engine;
|
||||
public:
|
||||
ScriptEngineThread(BatchProcessing *parent = nullptr);
|
||||
~ScriptEngineThread();
|
||||
void setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir);
|
||||
void start();
|
||||
void interrupt();
|
||||
signals:
|
||||
void newMessage(const QString &message, bool error);
|
||||
void finished();
|
||||
};
|
||||
|
||||
class FITSRecordModify;
|
||||
|
||||
class File : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
ScriptEngine *_engine = nullptr;
|
||||
QString _path;
|
||||
QString _root;
|
||||
QFileInfo _info;
|
||||
bool _fitsKeywordsLoaded = false;
|
||||
QStringList _fitsKeywords;
|
||||
QMultiHash<QString, FITSRecord> _fitsRecords;
|
||||
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);
|
||||
Q_INVOKABLE QString fileName() const;
|
||||
Q_INVOKABLE QString absoluteFilePath() const;
|
||||
Q_INVOKABLE QString absolutePath() const;
|
||||
Q_INVOKABLE QString relativeFilePath() const;
|
||||
Q_INVOKABLE QString relativePath() const;
|
||||
Q_INVOKABLE QString baseName() const;
|
||||
Q_INVOKABLE QString completeBaseName() const;
|
||||
Q_INVOKABLE QString suffix() const;
|
||||
Q_INVOKABLE qint64 size() const;
|
||||
Q_INVOKABLE QStringList fitsKeywords();
|
||||
Q_INVOKABLE QString fitsValue(const QString &key);
|
||||
Q_INVOKABLE QJSValue fitsValues(const QString &key);
|
||||
Q_INVOKABLE QJSValue fitsRecords();
|
||||
Q_INVOKABLE bool modifyFITSRecords(const FITSRecordModify *modify);
|
||||
Q_INVOKABLE bool isMarked() const;
|
||||
Q_INVOKABLE File* copy(const QString &newpath) const;
|
||||
Q_INVOKABLE bool move(const QString &newpath);
|
||||
Q_INVOKABLE File* convert(const QString &outpath, const QString &format, const QVariantMap ¶ms = QVariantMap());
|
||||
Q_INVOKABLE File* convertAsync(const QString &outpath, const QString &format, const QVariantMap ¶ms = QVariantMap());
|
||||
Q_INVOKABLE QJSValue stats();
|
||||
#ifdef PLATESOLVER
|
||||
Q_INVOKABLE QJSValue solve(bool updateHeader = false);
|
||||
Q_INVOKABLE QJSValue extractStars(bool hfr);
|
||||
#endif // PLATESOLVER
|
||||
};
|
||||
|
||||
class FITSRecordModify : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QStringList _remove;
|
||||
QVector<FITSRecord> _update;
|
||||
QVector<FITSRecord> _add;
|
||||
|
||||
friend class File;
|
||||
public:
|
||||
Q_INVOKABLE FITSRecordModify(){};
|
||||
Q_INVOKABLE void removeKeyword(const QString &key);
|
||||
Q_INVOKABLE void updateKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
|
||||
Q_INVOKABLE void addKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SCRIPTENGINE_H
|
||||
@@ -0,0 +1,18 @@
|
||||
core.log("This script convert any FITS file into XISF with ZSTD compression");
|
||||
|
||||
if(files.length == 0)
|
||||
{
|
||||
core.log("No input files");
|
||||
throw "";
|
||||
}
|
||||
|
||||
let compression = {"compressionType": "zstd+sh"};
|
||||
|
||||
for(file of files)
|
||||
{
|
||||
if(file.suffix() == "fits" || file.suffix() == "fit")
|
||||
{
|
||||
core.log("Converting " + file.fileName());
|
||||
file.convertAsync(file.relativeFilePath(), "XISF", compression);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// how to get input from user
|
||||
let d = core.getFloat("Getting float value");
|
||||
let i = core.getInt("Getting integer value");
|
||||
let s = core.getString("Getting string value");
|
||||
|
||||
// print user input
|
||||
core.log("Your input " + d + " " + i + " " + s);
|
||||
|
||||
for(file of files)
|
||||
{
|
||||
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
|
||||
{
|
||||
let keywords = file.fitsKeywords();
|
||||
let item = core.getItem(keywords, "Select keyword");
|
||||
core.log("You selected keyword " + item); core.log(file.fitsKeywords());
|
||||
|
||||
core.log("fileName() " + file.fileName());
|
||||
core.log("absoluteFilePath() " + file.absoluteFilePath());
|
||||
core.log("absolutePath() " + file.absolutePath());
|
||||
core.log("relativeFilePath() " + file.relativeFilePath());
|
||||
core.log("relativePath() " + file.relativePath());
|
||||
core.log("baseName() " + file.baseName());
|
||||
core.log("completeBase() " + file.completeBaseName());
|
||||
core.log("suffix() " + file.suffix());
|
||||
core.log("size() " + file.size());
|
||||
let stats = file.stats();
|
||||
core.log("Image statistics");
|
||||
core.log("\tMinimum: " + stats.min);
|
||||
core.log("\tMaximum: " + stats.max);
|
||||
core.log("\tMedian:" + stats.median);
|
||||
core.log("\tStandard deviation:" + stats.stddev);
|
||||
core.log("\tMedian Absolute Deviation:" + stats.mad);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
for(file of files)
|
||||
{
|
||||
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
|
||||
{
|
||||
let stats = file.stats();
|
||||
core.log("File: \"" + file.fileName() + "\" - median: " + stats.median);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
function checkFITS(key)
|
||||
{
|
||||
const noEditableKey = ["SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"];
|
||||
return noEditableKey.indexOf(key) < 0;
|
||||
}
|
||||
|
||||
if(files.length == 0)
|
||||
{
|
||||
core.log("No input files");
|
||||
throw "";
|
||||
}
|
||||
|
||||
let action = core.getItem(["UPDATE", "ADD", "REMOVE"], "Do you want update, add or remove record?");
|
||||
|
||||
let modify = new FITSRecordModify();
|
||||
|
||||
if(action == "UPDATE")
|
||||
{
|
||||
let keywords = files[0].fitsKeywords().filter(checkFITS);
|
||||
let keyword = core.getItem(keywords, "Select keyword to update");
|
||||
let value = files[0].fitsValue(keyword);
|
||||
if(isNaN(value))
|
||||
value = core.getString("Enter new value", value);
|
||||
else
|
||||
value = core.getFloat("Enter new value", value);
|
||||
modify.updateKeyword(keyword, value);
|
||||
}
|
||||
else if(action == "ADD")
|
||||
{
|
||||
let keyword = core.getString("Enter keyword to add");
|
||||
let value = core.getString("Enter new value");
|
||||
keyword = keyword.toUpperCase();
|
||||
modify.addKeyword(keyword, value);
|
||||
}
|
||||
else if(action == "REMOVE")
|
||||
{
|
||||
let keywords = files[0].fitsKeywords().filter(checkFITS);
|
||||
let keyword = core.getItem(keywords, "Select keyword to remove");
|
||||
modify.removeKeyword(keyword);
|
||||
}
|
||||
|
||||
for(file of files)
|
||||
{
|
||||
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
|
||||
{
|
||||
core.log("Modifing " + file.fileName());
|
||||
file.modifyFITSRecords(modify);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<RCC>
|
||||
<qresource prefix="/scripts">
|
||||
<file>example script</file>
|
||||
<file>convert to XISF</file>
|
||||
<file>median</file>
|
||||
<file>modify FITS header</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
extern int DEFAULT_WIDTH;
|
||||
extern double SATURATION;
|
||||
extern int FILTERING;
|
||||
|
||||
class EvenNumber : public QSpinBox
|
||||
{
|
||||
@@ -39,7 +40,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
|
||||
QSettings settings;
|
||||
|
||||
m_preloadImages = new QSpinBox(this);
|
||||
m_preloadImages->setRange(0, 8);
|
||||
m_preloadImages->setRange(0, 32);
|
||||
m_preloadImages->setValue(settings.value("settings/preloadimagecount", DEFAULT_WIDTH).toInt());
|
||||
m_preloadImages->setToolTip(tr("How many images are preloaded before and after current image."));
|
||||
|
||||
@@ -56,12 +57,30 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
|
||||
m_saturation->setValue(settings.value("settings/saturation", SATURATION * 100.0).toDouble());
|
||||
m_saturation->setToolTip(tr("Set threshold value that is considered saturated when showing statistics.\nFor RAW files you may set 22%"));
|
||||
|
||||
m_slideShowTime = new QDoubleSpinBox(this);
|
||||
m_slideShowTime->setMinimum(0.01);
|
||||
m_slideShowTime->setMaximum(10);
|
||||
m_slideShowTime->setSuffix(" s");
|
||||
m_slideShowTime->setValue(settings.value("settings/slideshowtime", 1.0).toDouble());
|
||||
m_slideShowTime->setSingleStep(0.1);
|
||||
|
||||
m_useNativeDialog = new QCheckBox(tr("Don't use native file dialog"), this);
|
||||
m_useNativeDialog->setChecked(QApplication::testAttribute(Qt::AA_DontUseNativeDialogs));
|
||||
|
||||
m_filtering = new QComboBox(this);
|
||||
m_filtering->addItems({tr("Nearest"), tr("Linear"), tr("Cubic")});
|
||||
m_filtering->setCurrentIndex(FILTERING);
|
||||
|
||||
m_qualityThumbnail = new QCheckBox(tr("Smooth thumbnails"), this);
|
||||
m_qualityThumbnail->setChecked(QUALITY_RESIZE);
|
||||
m_qualityThumbnail->setToolTip(tr("Use box filter when downsampling thumbnails instead of nearest. Slightly slower."));
|
||||
|
||||
layout->addRow(tr("Image preload count"), m_preloadImages);
|
||||
layout->addRow(tr("Thumbnails size"), m_thumSize);
|
||||
layout->addRow(tr("Saturation"), m_saturation);
|
||||
layout->addRow(tr("Slideshow interval"), m_slideShowTime);
|
||||
layout->addRow(tr("Image interpolation"), m_filtering);
|
||||
layout->addRow(m_qualityThumbnail);
|
||||
layout->addRow(m_useNativeDialog);
|
||||
//layout->addRow(new QLabel(tr("Changes in settings will take effect after program restart.")));
|
||||
|
||||
@@ -82,6 +101,8 @@ void SettingsDialog::loadSettings()
|
||||
THUMB_SIZE_BORDER_Y = THUMB_SIZE + 30;
|
||||
DEFAULT_WIDTH = settings.value("settings/preloadimagecount", DEFAULT_WIDTH).toInt();
|
||||
SATURATION = settings.value("settings/saturation", 95.0).toDouble() / 100.0;
|
||||
FILTERING = settings.value("settings/filtering", FILTERING).toInt();
|
||||
QUALITY_RESIZE = settings.value("settings/qualitythumbnail", QUALITY_RESIZE).toBool();
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, settings.value("settings/dontusenativedialogs", false).toBool());
|
||||
}
|
||||
|
||||
@@ -102,6 +123,11 @@ void SettingsDialog::saveSettings()
|
||||
settings.setValue("settings/preloadimagecount", m_preloadImages->value());
|
||||
settings.setValue("settings/dontusenativedialogs", m_useNativeDialog->isChecked());
|
||||
settings.setValue("settings/saturation", m_saturation->value());
|
||||
settings.setValue("settings/slideshowtime", m_slideShowTime->value());
|
||||
settings.setValue("settings/qualitythumbnail", m_qualityThumbnail->isChecked());
|
||||
QUALITY_RESIZE = m_qualityThumbnail->isChecked();
|
||||
FILTERING = m_filtering->currentIndex();
|
||||
settings.setValue("settings/filtering", FILTERING);
|
||||
SATURATION = m_saturation->value() / 100.0;
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, m_useNativeDialog->isChecked());
|
||||
if(DEFAULT_WIDTH != m_preloadImages->value())
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QDialog>
|
||||
#include <QSpinBox>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
|
||||
class SettingsDialog : public QDialog
|
||||
{
|
||||
@@ -19,8 +20,11 @@ private:
|
||||
|
||||
QSpinBox *m_preloadImages;
|
||||
QSpinBox *m_thumSize;
|
||||
QDoubleSpinBox *m_slideShowTime;
|
||||
QDoubleSpinBox *m_saturation;
|
||||
QCheckBox *m_useNativeDialog;
|
||||
QCheckBox *m_qualityThumbnail;
|
||||
QComboBox *m_filtering;
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_H
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#version 330
|
||||
|
||||
uniform sampler2D qt_Texture0;
|
||||
uniform ivec2 firstRed;
|
||||
in vec2 qt_TexCoord0;
|
||||
in vec2 center;
|
||||
layout(location = 0) out vec4 color;
|
||||
@@ -11,7 +10,7 @@ void main(void)
|
||||
{
|
||||
ivec2 texSize = textureSize(qt_Texture0, 0);
|
||||
ivec2 icenter = ivec2(center);
|
||||
ivec2 alternate = icenter % 2;
|
||||
ivec2 alternate = (icenter + firstRed) % 2;
|
||||
|
||||
// cross, checker, theta, phi
|
||||
const vec4 kA = vec4(-1.0, -1.5, 0.5, -1.0) / 8.0;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#version 330
|
||||
|
||||
uniform sampler2D qt_Texture0;
|
||||
in vec2 qt_Vertex;
|
||||
in vec2 qt_MultiTexCoord0;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#version 330
|
||||
|
||||
uniform sampler2D qt_Texture0;
|
||||
uniform sampler3D lut_table;
|
||||
uniform vec3 mtf_param[3];
|
||||
uniform vec2 unit_scale;
|
||||
uniform bool bw;
|
||||
uniform bool invert;
|
||||
uniform bool srgb;
|
||||
uniform bool false_color;
|
||||
uniform int filtering;
|
||||
in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
@@ -21,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)
|
||||
@@ -35,7 +35,8 @@ vec3 falsecolor(float color)
|
||||
vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0));//red
|
||||
color *= 5.0;
|
||||
int i = int(color);
|
||||
return mix(pallete[i], pallete[i+1], fract(color));
|
||||
float f = fract(color);
|
||||
return mix(pallete[i], pallete[i+1], f);// * (f * 0.5 + 0.5);
|
||||
}
|
||||
|
||||
vec3 checker()
|
||||
@@ -44,9 +45,104 @@ vec3 checker()
|
||||
return vec3(step(pattern.x * pattern.y, 0.0) * 0.25 + 0.25);
|
||||
}
|
||||
|
||||
vec4 cubic(float v)
|
||||
{
|
||||
vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
|
||||
vec4 s = n * n * n;
|
||||
float x = s.x;
|
||||
float y = s.y - 4.0 * s.x;
|
||||
float z = s.z - 4.0 * s.y + 6.0 * s.x;
|
||||
float w = 6.0 - x - y - z;
|
||||
return vec4(x, y, z, w) * (1.0/6.0);
|
||||
}
|
||||
|
||||
vec4 textureBicubic(sampler2D sampler, vec2 texCoords)
|
||||
{
|
||||
vec2 texSize = vec2(textureSize(sampler, 0));
|
||||
vec2 invTexSize = 1.0 / texSize;
|
||||
|
||||
texCoords = texCoords * texSize - 0.5;
|
||||
|
||||
vec2 fxy = fract(texCoords);
|
||||
texCoords -= fxy;
|
||||
|
||||
vec4 xcubic = cubic(fxy.x);
|
||||
vec4 ycubic = cubic(fxy.y);
|
||||
|
||||
vec4 c = texCoords.xxyy + vec2 (-0.5, +1.5).xyxy;
|
||||
|
||||
vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
|
||||
vec4 offset = c + vec4 (xcubic.yw, ycubic.yw) / s;
|
||||
|
||||
offset *= invTexSize.xxyy;
|
||||
|
||||
vec4 sample0 = texture(sampler, offset.xz);
|
||||
vec4 sample1 = texture(sampler, offset.yz);
|
||||
vec4 sample2 = texture(sampler, offset.xw);
|
||||
vec4 sample3 = texture(sampler, offset.yw);
|
||||
|
||||
float sx = s.x / (s.x + s.y);
|
||||
float sy = s.z / (s.z + s.w);
|
||||
|
||||
return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
|
||||
}
|
||||
|
||||
vec4 textureCatmul(sampler2D sampler, vec2 texCoords)
|
||||
{
|
||||
ivec2 texSize = textureSize(sampler, 0);
|
||||
|
||||
texCoords = texCoords * vec2(texSize) - 0.5;
|
||||
|
||||
ivec2 texel = ivec2(floor(texCoords));
|
||||
vec2 fra = fract(texCoords);
|
||||
texSize -= 1;
|
||||
|
||||
const mat4 CatMul = mat4(0, 1, 0, 0, -0.5, 0, 0.5, 0, 1, -2.5, 2, -0.5, -0.5, 1.5, -1.5, 0.5);
|
||||
vec4 xx = CatMul * vec4(1.0, fra.x, fra.x*fra.x, fra.x*fra.x*fra.x);
|
||||
vec4 yy = CatMul * vec4(1.0, fra.y, fra.y*fra.y, fra.y*fra.y*fra.y);
|
||||
|
||||
vec4 a00 = texelFetch(sampler, clamp(texel + ivec2(-1, -1), ivec2(0, 0), texSize), 0) * xx.x;
|
||||
vec4 a01 = texelFetch(sampler, clamp(texel + ivec2( 0, -1), ivec2(0, 0), texSize), 0) * xx.y;
|
||||
vec4 a02 = texelFetch(sampler, clamp(texel + ivec2( 1, -1), ivec2(0, 0), texSize), 0) * xx.z;
|
||||
vec4 a03 = texelFetch(sampler, clamp(texel + ivec2( 2, -1), ivec2(0, 0), texSize), 0) * xx.w;
|
||||
vec4 a10 = texelFetch(sampler, clamp(texel + ivec2(-1, 0), ivec2(0, 0), texSize), 0) * xx.x;
|
||||
vec4 a11 = texelFetch(sampler, clamp(texel + ivec2( 0, 0), ivec2(0, 0), texSize), 0) * xx.y;
|
||||
vec4 a12 = texelFetch(sampler, clamp(texel + ivec2( 1, 0), ivec2(0, 0), texSize), 0) * xx.z;
|
||||
vec4 a13 = texelFetch(sampler, clamp(texel + ivec2( 2, 0), ivec2(0, 0), texSize), 0) * xx.w;
|
||||
vec4 a20 = texelFetch(sampler, clamp(texel + ivec2(-1, 1), ivec2(0, 0), texSize), 0) * xx.x;
|
||||
vec4 a21 = texelFetch(sampler, clamp(texel + ivec2( 0, 1), ivec2(0, 0), texSize), 0) * xx.y;
|
||||
vec4 a22 = texelFetch(sampler, clamp(texel + ivec2( 1, 1), ivec2(0, 0), texSize), 0) * xx.z;
|
||||
vec4 a23 = texelFetch(sampler, clamp(texel + ivec2( 2, 1), ivec2(0, 0), texSize), 0) * xx.w;
|
||||
vec4 a30 = texelFetch(sampler, clamp(texel + ivec2(-1, 2), ivec2(0, 0), texSize), 0) * xx.x;
|
||||
vec4 a31 = texelFetch(sampler, clamp(texel + ivec2( 0, 2), ivec2(0, 0), texSize), 0) * xx.y;
|
||||
vec4 a32 = texelFetch(sampler, clamp(texel + ivec2( 1, 2), ivec2(0, 0), texSize), 0) * xx.z;
|
||||
vec4 a33 = texelFetch(sampler, clamp(texel + ivec2( 2, 2), ivec2(0, 0), texSize), 0) * xx.w;
|
||||
|
||||
vec4 c = vec4(0.0);
|
||||
c += (a00 + a01 + a02 + a03) * yy.x;
|
||||
c += (a10 + a11 + a12 + a13) * yy.y;
|
||||
c += (a20 + a21 + a22 + a23) * yy.z;
|
||||
c += (a30 + a31 + a32 + a33) * yy.w;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void main(void)
|
||||
{
|
||||
color = texture(qt_Texture0, qt_TexCoord0);
|
||||
switch(filtering)
|
||||
{
|
||||
case 0://nearest
|
||||
color = texelFetch(qt_Texture0, ivec2(qt_TexCoord0 * vec2(textureSize(qt_Texture0, 0))), 0);
|
||||
break;
|
||||
default:
|
||||
case 1://bilinear
|
||||
color = texture(qt_Texture0, qt_TexCoord0);
|
||||
break;
|
||||
case 2://catmul bicubic
|
||||
color = textureCatmul(qt_Texture0, qt_TexCoord0);
|
||||
break;
|
||||
}
|
||||
|
||||
color.rgb = color.rgb * unit_scale.x + unit_scale.y;
|
||||
if(bw)color = color.rrra;
|
||||
color = MTF(color, vec4(mtf_param[0], 0.0), vec4(mtf_param[1], 0.5), vec4(mtf_param[2], 1.0));
|
||||
@@ -56,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);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#version 330
|
||||
|
||||
uniform sampler2D qt_Texture0;
|
||||
in vec2 qt_Vertex;
|
||||
in vec2 qt_MultiTexCoord0;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>debayer.frag</file>
|
||||
<file>debayer.vert</file>
|
||||
<file>image.frag</file>
|
||||
<file>image.vert</file>
|
||||
<file>thumb.frag</file>
|
||||
<file>thumb.vert</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
#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 ¶meters)
|
||||
{
|
||||
auto profile = parameters;
|
||||
profile.partition = false;
|
||||
_solver->setParameters(profile);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#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 ¶meters);
|
||||
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
|
||||
@@ -4,6 +4,10 @@
|
||||
<launchable type="desktop-id">space.nouspiro.tenmon.desktop</launchable>
|
||||
<name>Tenmon</name>
|
||||
<summary>FITS/XISF image viewer, converter, index and search</summary>
|
||||
<developer id="nouspiro.space">
|
||||
<name>Dušan Poizl</name>
|
||||
</developer>
|
||||
<developer_name>Dušan Poizl</developer_name>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<description>
|
||||
@@ -11,7 +15,7 @@
|
||||
<ul>
|
||||
<li>FITS 8, 16, 32 bit integer and 32, 64 bit float</li>
|
||||
<li>XISF 8, 16, 32 bit integer and 32, 64 bit float</li>
|
||||
<li>RAW CR2, DNG, NEF</li>
|
||||
<li>RAW CR2/CR3, DNG, NEF</li>
|
||||
<li>JPEG, PNG, BMP, GIF, PBM, PGM, PPM and SVG images</li>
|
||||
</ul>
|
||||
<p>Features of application:</p>
|
||||
@@ -43,12 +47,66 @@
|
||||
<url type="bugtracker">https://github.com/flathub/space.nouspiro.tenmon/issues</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Main window with image</caption>
|
||||
<image>https://nouspiro.space/wp-content/uploads/2022/04/tenmon-1024x579.png</image>
|
||||
</screenshot>
|
||||
<screenshot type="default">
|
||||
<caption>Thumnail view</caption>
|
||||
<image>https://nouspiro.space/wp-content/uploads/2022/12/tenmon2-1024x645.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<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>
|
||||
<li>Batch processing with JavaScript</li>
|
||||
<li>Opening directory recursively</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="20240201" date="2024-02-01">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Smooth thumbnails</li>
|
||||
<li>Respect ROWORDER</li>
|
||||
<li>Bugfixes</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="20240108" date="2024-01-08">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Update to Qt6</li>
|
||||
<li>Add support for CR3 RAW files</li>
|
||||
<li>Slideshow</li>
|
||||
<li>Improved rapid image view</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="20231116" date="2023-11-16">
|
||||
<description>
|
||||
<ul>
|
||||
|
||||
@@ -105,43 +105,44 @@ void STFSlider::paintEvent(QPaintEvent *event)
|
||||
|
||||
void STFSlider::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if(std::abs(m_blackPoint*width() - event->x()) < 5 ||
|
||||
std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - event->x()) < 5 ||
|
||||
std::abs(m_whitePoint*width() - event->x()) < 5)
|
||||
const qreal x = event->position().x();
|
||||
if(std::abs(m_blackPoint*width() - x) < 5 ||
|
||||
std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - x) < 5 ||
|
||||
std::abs(m_whitePoint*width() - x) < 5)
|
||||
setCursor(Qt::SplitHCursor);
|
||||
else
|
||||
unsetCursor();
|
||||
|
||||
qreal x = (qreal)event->x()/width();
|
||||
qreal xw = x/width();
|
||||
if(event->modifiers() & Qt::ShiftModifier && !m_fineTune)
|
||||
{
|
||||
m_fineTune = true;
|
||||
m_fineTuneX = x;
|
||||
m_fineTuneX = xw;
|
||||
}
|
||||
if(!(event->modifiers() & Qt::ShiftModifier) && m_fineTune)
|
||||
m_fineTune = false;
|
||||
|
||||
if(m_fineTune)
|
||||
{
|
||||
x = m_fineTuneX + (x - m_fineTuneX) * 0.2;
|
||||
xw = m_fineTuneX + (xw - m_fineTuneX) * 0.2;
|
||||
}
|
||||
|
||||
switch(m_grabbed)
|
||||
{
|
||||
case 0:
|
||||
m_blackPoint = clamp(x);
|
||||
m_blackPoint = clamp(xw);
|
||||
m_whitePoint = std::max(m_whitePoint, m_blackPoint);
|
||||
QToolTip::showText(event->globalPos(), QString::number(m_blackPoint), this);
|
||||
QToolTip::showText(event->globalPosition().toPoint(), QString::number(m_blackPoint), this);
|
||||
break;
|
||||
case 1:
|
||||
m_midPoint = (x - m_blackPoint) / (m_whitePoint - m_blackPoint);
|
||||
m_midPoint = (xw - m_blackPoint) / (m_whitePoint - m_blackPoint);
|
||||
m_midPoint = clamp(m_midPoint);
|
||||
QToolTip::showText(event->globalPos(), QString::number(m_midPoint), this);
|
||||
QToolTip::showText(event->globalPosition().toPoint(), QString::number(m_midPoint), this);
|
||||
break;
|
||||
case 2:
|
||||
m_whitePoint = clamp(x);
|
||||
m_whitePoint = clamp(xw);
|
||||
m_blackPoint = std::min(m_blackPoint, m_whitePoint);
|
||||
QToolTip::showText(event->globalPos(), QString::number(m_whitePoint), this);
|
||||
QToolTip::showText(event->globalPosition().toPoint(), QString::number(m_whitePoint), this);
|
||||
break;
|
||||
}
|
||||
if(m_grabbed >= 0)
|
||||
@@ -153,17 +154,18 @@ void STFSlider::mouseMoveEvent(QMouseEvent *event)
|
||||
|
||||
void STFSlider::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
const qreal x = event->position().x();
|
||||
if(event->modifiers() & Qt::ShiftModifier)
|
||||
{
|
||||
m_fineTune = true;
|
||||
m_fineTuneX = (qreal)event->x()/width();
|
||||
m_fineTuneX = x/width();
|
||||
}
|
||||
|
||||
if(std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - event->x()) < 5)
|
||||
if(std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - x) < 5)
|
||||
m_grabbed = 1;
|
||||
else if(std::abs(m_blackPoint*width() - event->x()) < 5)
|
||||
else if(std::abs(m_blackPoint*width() - x) < 5)
|
||||
m_grabbed = 0;
|
||||
else if(std::abs(m_whitePoint*width() - event->x()) < 5)
|
||||
else if(std::abs(m_whitePoint*width() - x) < 5)
|
||||
m_grabbed = 2;
|
||||
else
|
||||
m_grabbed = -1;
|
||||
|
||||
@@ -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)
|
||||
@@ -120,12 +129,15 @@ void StretchToolbar::stretchImage(Image *img)
|
||||
mad = stats.m_mad[i];
|
||||
max = stats.m_max[i];
|
||||
median /= img->rawImage()->norm();
|
||||
bool a = median > 0.5 ? true : false;
|
||||
mad /= img->rawImage()->norm();
|
||||
max /= img->rawImage()->norm();
|
||||
float bp = median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA;
|
||||
float mid = MTF(median - bp, TARGET_BACKGROUND);
|
||||
max = 1.0f;// /= img->rawImage()->norm();
|
||||
float bp = a || mad == 0.0f ? 0.0f : std::clamp(median + mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
|
||||
if(a && mad != 0.0f)
|
||||
max = std::clamp(median - mad * BLACK_POINT_SIGMA * MAD_TO_SIGMA, 0.0, 1.0);
|
||||
float mid = !a ? MTF(median - bp, TARGET_BACKGROUND) : MTF(TARGET_BACKGROUND, max - median);
|
||||
m_mtfParam.blackPoint[i-o] = bp;
|
||||
m_mtfParam.midPoint[i-o] = mid / max;
|
||||
m_mtfParam.midPoint[i-o] = mid;// / max;
|
||||
m_mtfParam.whitePoint[i-o] = max;
|
||||
bp2 += bp;
|
||||
mid2 += mid;
|
||||
|
||||
@@ -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();
|
||||
|
||||