Compare commits

...

43 Commits

Author SHA1 Message Date
nou f9f005e7ea Another test 2025-07-16 23:17:22 +02:00
nou 5ba6b4863c Fix metainfo linter 2025-07-16 09:07:08 +02:00
nou 6c7e078340 Testing filetree crash on ARM 2025-07-16 07:44:19 +02:00
nou b58559a18a Update modify FITS header script 2025-07-13 10:43:15 +02:00
nou 2ac14a6c04 Fix thumbnailer compilation 2025-07-13 10:42:59 +02:00
nou b84256625c Add stellarsolver6 as name 2025-06-12 16:48:09 +02:00
nou 202a2b11b7 Add marked files in batch processing 2025-06-09 19:09:50 +02:00
nou 32f192ed7e Add draw grid button 2025-05-31 00:19:15 +02:00
nou a0422683bd Move source files to src directory 2025-05-30 16:49:33 +02:00
nou ce67b35bfa Mention OpenNGC 2025-05-29 23:44:55 +02:00
nou f016500f12 Include ngc db 2025-05-29 17:39:13 +02:00
nou 6069ebbbac Refractor drawing grid 2025-05-27 16:26:03 +02:00
nou e587d84e05 Remove that empty action 2025-05-26 17:05:19 +02:00
nou c01f2e328a Add sky grid painting 2025-05-26 15:50:37 +02:00
nou 8b498bbe73 Prefer writing keyword as integer 2025-05-24 23:13:14 +02:00
nou c6bc792ff7 Update metainfo 2025-04-29 16:03:30 +02:00
nou 1a307d82f9 Update translations 2025-04-29 15:59:59 +02:00
nou 8c5e2b2ebf Update help 2025-04-29 14:07:16 +02:00
nou 03ad135ef0 Fix bestFit line color 2025-04-29 13:31:42 +02:00
nou 2a78a9a41d Add XISF mime type 2025-04-29 13:15:45 +02:00
nou 1a214a169e Blind and update question for platesolve script 2025-04-28 17:25:07 +02:00
nou f8704c51d8 FITS hightlight settings 2025-04-28 17:06:24 +02:00
nou 3feee0256c New core.question script method 2025-04-28 17:04:45 +02:00
nou 53472d807c Adding new scripts 2025-04-27 22:54:29 +02:00
nou 9f06269aa4 Improve chart graph 2025-04-27 17:43:32 +02:00
nou 78f242d808 Extending script plot() function 2025-04-23 13:37:47 +02:00
nou e6bab45a89 Fix index of subimage for XISF 2025-04-15 20:58:03 +02:00
nou 58286d52c5 TextFile scripting 2025-04-15 11:09:23 +02:00
nou bac1963fa4 Add stretch toolbar actions to view menu 2025-04-11 17:05:31 +02:00
nou 2415717ce0 Add STFSlider ability to be vertical 2025-04-10 23:09:59 +02:00
nou e7acbca01e Color highlight FITS header 2025-04-10 19:58:29 +02:00
nou 7c4118b0b6 Fix bug in script solver 2025-04-10 00:34:14 +02:00
nou 8178efdafd Reload image when header is updated 2025-04-09 14:58:23 +02:00
nou 90026f931f Add open file and open file location to DB view 2025-04-02 20:27:59 +02:00
nou eee4613b25 Add plot() script method 2025-04-02 20:27:19 +02:00
nou 24a9e96bbf Streamline standalone thumbnailer 2025-04-02 15:24:41 +02:00
nou 5af5f4f068 Navigation menu 2025-04-02 12:24:17 +02:00
nou 85f9822b96 Fix prevSubImage 2025-03-25 18:26:08 +01:00
nou 7fc6c64fd7 Make help windows non modal 2025-03-24 22:09:18 +01:00
nou 4488c2e6af Always make 4 channels 2025-03-24 22:08:54 +01:00
nou 0047607c1d Add loading sub images 2025-03-23 13:33:34 +01:00
nou 45c368bbbb Remove usage of SLOT() and SIGNAL() 2025-03-19 13:50:39 +01:00
nou c96cb86a29 Show relative path in title bar for when browsing dir recursive 2025-03-19 13:14:04 +01:00
91 changed files with 5313 additions and 1176 deletions
+35 -33
View File
@@ -17,44 +17,45 @@ if(SANITIZE_ADDRESS_LEAK)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak")
endif(SANITIZE_ADDRESS_LEAK)
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml REQUIRED)
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml Charts 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 REQUIRED)
find_library(LCMS2_LIB lcms2 REQUIRED)
find_library(STELLARSOLVER_LIB stellarsolver)
find_library(STELLARSOLVER_LIB NAMES stellarsolver stellarsolver6)
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
imageinfodata.cpp imageinfodata.h
imageringlist.cpp imageringlist.h
imagescrollarea.cpp imagescrollarea.h
imagewidget.h imagewidget.cpp
loadimage.h loadimage.cpp
loadrunable.cpp loadrunable.h
main.cpp
mainwindow.cpp mainwindow.h
markedfiles.cpp markedfiles.h
mtfparam.h
rawimage.cpp rawimage.h
rawimage_sse.cpp
scriptengine.cpp scriptengine.h
settingsdialog.cpp settingsdialog.h
statusbar.cpp statusbar.h
stfslider.cpp stfslider.h
stretchtoolbar.cpp stretchtoolbar.h
tfloat16.h
src/about.cpp src/about.h
src/batchprocessing.cpp src/batchprocessing.h src/batchprocessing.ui
src/chartgraph.h src/chartgraph.cpp
src/database.cpp src/database.h
src/databaseview.cpp src/databaseview.h
src/delete.cpp
src/filesystemwidget.cpp src/filesystemwidget.h
src/histogram.cpp src/histogram.h
src/httpdownloader.h src/httpdownloader.cpp
src/imageinfo.cpp src/imageinfo.h
src/imageinfodata.cpp src/imageinfodata.h
src/imageringlist.cpp src/imageringlist.h
src/imagescrollarea.cpp src/imagescrollarea.h
src/imagewidget.h src/imagewidget.cpp
src/loadimage.h src/loadimage.cpp
src/loadrunable.cpp src/loadrunable.h
src/main.cpp
src/mainwindow.cpp src/mainwindow.h
src/markedfiles.cpp src/markedfiles.h
src/mtfparam.h
src/rawimage.cpp src/rawimage.h
src/rawimage_sse.cpp
src/scriptengine.cpp src/scriptengine.h
src/settingsdialog.cpp src/settingsdialog.h
src/statusbar.cpp src/statusbar.h
src/stfslider.cpp src/stfslider.h
src/stretchtoolbar.cpp src/stretchtoolbar.h
src/tfloat16.h
thumbnailer/genthumbnail.cpp thumbnailer/genthumbnail.h
)
@@ -95,14 +96,14 @@ if(STELLARSOLVER_INCLUDE AND 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
src/solver.cpp src/solver.h
src/platesolving.cpp src/platesolving.h src/platesolving.ui
src/platesolvingsettings.cpp src/platesolvingsettings.h src/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 ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml Qt6::Charts ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
if(APPLE)
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
elseif(UNIX)
@@ -129,6 +130,7 @@ if(UNIX AND NOT APPLE)
install(FILES space.nouspiro.tenmon.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
install(FILES resources/space.nouspiro.tenmon.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps")
install(FILES resources/space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
install(FILES space.nouspiro.tenmon.xisf.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/mime/packages")
endif()
install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo")
endif(UNIX AND NOT APPLE)
+2
View File
@@ -29,3 +29,5 @@ Then to build run standard cmake sequence
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.
Using OpenNGC database https://github.com/mattiaverga/OpenNGC under CC-BY-SA-4.0 https://creativecommons.org/licenses/by-sa/4.0/
+42
View File
@@ -184,6 +184,48 @@ Location of this directory is on Windows: "C:/Users/<USER>/AppData/Roaming
core.log(JSON.stringify(profile));</pre></li>
<li><b>setSolverProfile(index)</b> set solver profile by index. Valid values are from 1 to 8 as in GUI.</li>
<li><b>setSolverProfile(profile)</b> set solver profile. Parameter is same as object returned by <i>getSolverProfile()</i> method</li>
<li><b>question(question, buttons, title = "")</b> show dialog with question. First argument <i>question</i> is string. Second argument <i>buttons</i>
is array of following strings. "ok", "yes", "no", "yesall", "noall", "abort", "retry", "ignore", "cancel", "discard", "apply", "reset" Third argument <i>title</i> is optional string that show in title bar.
It return button that was clicked as a string.
<pre>var button = core.question("Apply to all files?", ["yes", "no"]);</pre>
</li>
<li><b>plot(graph)</b> this method show graph defined by JS object.
<pre>
var chart = {
"title": "Chart title", // Title that will show on top of chart
"legend":
{
"visible": true,// default is true
"align": "left" // allowed values are "top", "right", "bottom", "left". default is "top"
},
"series":[ // array of data series
{
"title": "HFR",
"type": "bar", // type of the serie. Can be one of "line", "points", "linePoints", "bar"
"y":[2.5,3.1,2.6,2.2] // array of values
},
{
"title": "Ecc",
"y":[0.37, 0.4, 0.35, 0.25],
"color": "red" // color of serie. It can be name of color like "green" or RGB hex value "#ccddff"
},
{
"title": "Stars",
"type": "points", // type of serie. can be "line", "points", "linePoints" and "bar". Default is "line"
"shape": "star", // shape of markers. valid only for points
"x":[1, 2.5, 3.5, 6],// line, points and linePoints can also have "x" values. Otherwise it assume sequence 0,1,2 ...
"y":[523,412,487,510],
"y2": true, // if set to true this serie will use secondary Y axis
"bestFit": true, // show best fit line
"color": "#0000ff"
}
]
};
core.plot(chart);
</pre>
</li>
</ul>
<h4>File</h4>
+48
View File
@@ -135,6 +135,54 @@ Le deuxième paramètre est la valeur par défaut dans la zone de saisie. Les tr
<li><b>getItem(items)</b> affiche une boîte de dialogue de sélection qui permet de sélectionner un élément dans un tableau d'éléments. Lorsque vous appuyez sur Annuler, il renvoie Undefined.</li>
<li><b>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>
<li><b>getSolverProfile()</b> return solver profile as Object.
<pre>var profile = core.getSolverProfile();
core.log(JSON.stringify(profile));</pre></li>
<li><b>setSolverProfile(index)</b> set solver profile by index. Valid values are from 1 to 8 as in GUI.</li>
<li><b>setSolverProfile(profile)</b> set solver profile. Parameter is same as object returned by <i>getSolverProfile()</i> method</li>
<li><b>question(question, buttons, title = "")</b> show dialog with question. First argument <i>question</i> is string. Second argument <i>buttons</i>
is array of following strings. "ok", "yes", "no", "yesall", "noall", "abort", "retry", "ignore", "cancel", "discard", "apply", "reset" Third argument <i>title</i> is optional string that show in title bar.
It return button that was clicked as a string.
<pre>var button = core.question("Apply to all files?", ["yes", "no"]);</pre>
</li>
<li><b>plot(graph)</b> this method show graph defined by JS object.
<pre>
var chart = {
"title": "Chart title", // Title that will show on top of chart
"legend":
{
"visible": true,// default is true
"align": "left" // allowed values are "top", "right", "bottom", "left". default is "top"
},
"series":[ // array of data series
{
"title": "HFR",
"type": "bar", // type of the serie. Can be one of "line", "points", "linePoints", "bar"
"y":[2.5,3.1,2.6,2.2] // array of values
},
{
"title": "Ecc",
"y":[0.37, 0.4, 0.35, 0.25],
"color": "red" // color of serie. It can be name of color like "green" or RGB hex value "#ccddff"
},
{
"title": "Stars",
"type": "points", // type of serie. can be "line", "points", "linePoints" and "bar". Default is "line"
"shape": "star", // shape of markers. valid only for points
"x":[1, 2.5, 3.5, 6],// line, points and linePoints can also have "x" values. Otherwise it assume sequence 0,1,2 ...
"y":[523,412,487,510],
"y2": true, // if set to true this serie will use secondary Y axis
"bestFit": true, // show best fit line
"color": "#0000ff"
}
]
};
core.plot(chart);
</pre>
</li>
</ul>
<h4>File</h4>
+48
View File
@@ -126,6 +126,54 @@ V skripte je dostupný globálny objekt nazvaný <b>core</b> ktorý má nasledov
<li><b>getItem(items)</b> ukáže dialog pre výber jednej hodnoty z poľa hodnôt. Vracia vybranú hodnotu ako String alebo ak je stlačené tlačidlo zrušiť vráti Undefined.</li>
<li><b>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>
<li><b>getSolverProfile()</b> return solver profile as Object.
<pre>var profile = core.getSolverProfile();
core.log(JSON.stringify(profile));</pre></li>
<li><b>setSolverProfile(index)</b> set solver profile by index. Valid values are from 1 to 8 as in GUI.</li>
<li><b>setSolverProfile(profile)</b> set solver profile. Parameter is same as object returned by <i>getSolverProfile()</i> method</li>
<li><b>question(question, buttons, title = "")</b> show dialog with question. First argument <i>question</i> is string. Second argument <i>buttons</i>
is array of following strings. "ok", "yes", "no", "yesall", "noall", "abort", "retry", "ignore", "cancel", "discard", "apply", "reset" Third argument <i>title</i> is optional string that show in title bar.
It return button that was clicked as a string.
<pre>var button = core.question("Apply to all files?", ["yes", "no"]);</pre>
</li>
<li><b>plot(graph)</b> this method show graph defined by JS object.
<pre>
var chart = {
"title": "Chart title", // Title that will show on top of chart
"legend":
{
"visible": true,// default is true
"align": "left" // allowed values are "top", "right", "bottom", "left". default is "top"
},
"series":[ // array of data series
{
"title": "HFR",
"type": "bar", // type of the serie. Can be one of "line", "points", "linePoints", "bar"
"y":[2.5,3.1,2.6,2.2] // array of values
},
{
"title": "Ecc",
"y":[0.37, 0.4, 0.35, 0.25],
"color": "red" // color of serie. It can be name of color like "green" or RGB hex value "#ccddff"
},
{
"title": "Stars",
"type": "points", // type of serie. can be "line", "points", "linePoints" and "bar". Default is "line"
"shape": "star", // shape of markers. valid only for points
"x":[1, 2.5, 3.5, 6],// line, points and linePoints can also have "x" values. Otherwise it assume sequence 0,1,2 ...
"y":[523,412,487,510],
"y2": true, // if set to true this serie will use secondary Y axis
"bestFit": true, // show best fit line
"color": "#0000ff"
}
]
};
core.plot(chart);
</pre>
</li>
</ul>
<h4>File</h4>
+2
View File
@@ -1,5 +1,7 @@
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
find_program(XDG-ICON-RESOURCE_EXECUTABLE xdg-icon-resource)
find_program(XDG-MIME xdg-mime)
execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor space.nouspiro.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 resources/space.nouspiro.tenmon.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 128 resources/space.nouspiro.tenmon_128.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-MIME} install --novendor space.nouspiro.tenmon.xisf.xml WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
+141
View File
@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.699999 12.699999"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="grid.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="9.5144352"
inkscape:cx="39.361243"
inkscape:cy="25.067174"
inkscape:window-width="2510"
inkscape:window-height="1371"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<inkscape:path-effect
effect="skeletal"
id="path-effect636"
is_visible="true"
lpeversion="1"
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect632"
is_visible="true"
lpeversion="1"
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect628"
is_visible="true"
lpeversion="1"
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="bspline"
id="path-effect624"
is_visible="true"
lpeversion="1"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
<inkscape:path-effect
effect="spiro"
id="path-effect620"
is_visible="true"
lpeversion="1" />
</defs>
<g
inkscape:label="Vrstva 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
d="M -5,-5 13,13"
id="path616"
sodipodi:nodetypes="cc" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none"
id="path643"
cx="-4.9824347"
cy="-4.9865055"
r="12.973718" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1665"
cx="-4.9600825"
cy="-4.9741392"
r="17.086035" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1667"
cx="-5.0079365"
cy="-5.0034046"
r="21.147657" />
<path
style="fill:none;stroke:#000000;stroke-width:0.467;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
d="M 14.371451,3.5622727 -4.9904999,-5.0054782 4.2432806,13.903978"
id="path1734" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1736"
cx="-5.155458"
cy="-5.1256938"
r="9.6808758" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
View File
Binary file not shown.
+5 -3
View File
@@ -19,14 +19,16 @@
<file>../translations/tenmon_pt_BR.qm</file>
<file alias="help">../about/help_en</file>
<file>colormap.png</file>
<file>ngc.db</file>
<file>grid.svg</file>
</qresource>
<qresource prefix="/" lang="en">
<qresource lang="en" prefix="/">
<file alias="help">../about/help_en</file>
</qresource>
<qresource prefix="/" lang="sk">
<qresource lang="sk" prefix="/">
<file alias="help">../about/help_sk</file>
</qresource>
<qresource prefix="/" lang="fr">
<qresource lang="fr" prefix="/">
<file alias="help">../about/help_fr</file>
</qresource>
</RCC>
-6
View File
@@ -1,11 +1,5 @@
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)
+49
View File
@@ -0,0 +1,49 @@
core.log("Measure HFR and eccentricity of stars");
var chart = {
"title": "Measure stars",
"legend": {"visible": true, "align": "left"},
"series": [
{
"title": "HFR",
"type": "bar",
"y":[]
},
{
"title": "Ecc",
"type": "bar",
"y":[]
},
{
"title": "Star count",
"type": "linePoints",
"y":[],
"y2": true,
"bestFit": true
}
]
};
core.setSolverProfile(5);
for(file of files)
{
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
{
var stars = file.extractStars(true);
var sumHFR = 0;
var ecc = 0;
for(star of stars)
{
sumHFR += star.HFR;
ecc += Math.sqrt(1 - (star.b * star.b) / (star.a * star.a));
}
chart.series[0].y.push(sumHFR / stars.length);
chart.series[1].y.push(ecc / stars.length);
chart.series[2].y.push(stars.length);
core.log(file.fileName() + " Stars:" + stars.length + " HFR: " + sumHFR / stars.length + " Ecc: " + ecc / stars.length);
}
}
core.plot(chart);
+47 -12
View File
@@ -1,3 +1,5 @@
core.log("Script to modify FITS header in FITS and XISF files");
function checkFITS(key)
{
const noEditableKey = ["SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"];
@@ -10,10 +12,12 @@ if(files.length == 0)
throw "";
}
let action = core.getItem(["UPDATE", "ADD", "REMOVE"], "Do you want update, add or remove record?");
let action = core.getItem(["UPDATE", "UPDATE_ADD", "ADD", "REMOVE"], "Do you want update, add or remove record?");
let modify = new FITSRecordModify();
let proceed = false;
if(action == "UPDATE")
{
let keywords = files[0].fitsKeywords().filter(checkFITS);
@@ -23,28 +27,59 @@ if(action == "UPDATE")
value = core.getString("Enter new value", value);
else
value = core.getFloat("Enter new value", value);
modify.updateKeyword(keyword, value);
if(keyword && value)
{
proceed = true;
modify.updateKeyword(keyword, value);
}
}
else if(action == "UPDATE_ADD")
{
let keyword = core.getString("Enter keyword to update");
let value = core.getString("Enter new value");
if(keyword && value)
{
proceed = true;
keyword = keyword.toUpperCase();
modify.updateKeyword(keyword, value);
}
}
else if(action == "ADD")
{
let keyword = core.getString("Enter keyword to add");
let value = core.getString("Enter new value");
keyword = keyword.toUpperCase();
modify.addKeyword(keyword, value);
if(keyword && value)
{
proceed = true;
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")
if(keyword)
{
core.log("Modifing " + file.fileName());
file.modifyFITSRecords(modify);
proceed = true;
modify.removeKeyword(keyword);
}
}
if(proceed)
{
for(file of files)
{
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
{
core.log("Modifing " + file.fileName());
file.modifyFITSRecords(modify);
}
}
}
else
{
core.log("Canceled");
}
+20
View File
@@ -0,0 +1,20 @@
core.log("Plate solve and update solution");
var first = true;
var update = core.question("Update FITS header with solution?", ["yes", "no"], "Update FITS header") == "yes";
var blind = core.question("Do blind solve every image?", ["yes", "no"], "Blind solve?") == "yes";
for(file of files)
{
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
{
var solution = file.solve(update);
if(first && !blind)
{
core.setStartingSolution(solution);
first = false;
}
core.log(file.fileName() + " " + "RA: " + (solution.ra / 15) + "h DEC: " + solution.dec + "deg");
}
}
+2
View File
@@ -4,5 +4,7 @@
<file>convert to XISF</file>
<file>median</file>
<file>modify FITS header</file>
<file>measure HFR</file>
<file>plate solve</file>
</qresource>
</RCC>
+12 -1
View File
@@ -58,6 +58,17 @@
</screenshots>
<content_rating type="oars-1.1"/>
<releases>
<release version="20250429" date="2025-04-29">
<description>
<ul>
<li>Add ability to load multiple images in single file</li>
<li>New plot() and question() script methods</li>
<li>Color highlight of FITS keywords</li>
<li>New scripts to batch platesolve and measure stars</li>
<li>Stretch toolbar can now be vertical</li>
</ul>
</description>
</release>
<release version="20250318" date="2025-03-18">
<description>
<ul>
@@ -107,7 +118,7 @@
</release>
<release version="20240816" date="2024-08-16">
<description>
Fix saving image
<p>Fix saving image</p>
</description>
</release>
<release version="20240616" date="2024-06-16">
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="image/x-xisf">
<comment>Extensible Image Serialization Format</comment>
<glob pattern="*.xisf"/>
</mime-type>
</mime-info>
+2
View File
@@ -32,6 +32,8 @@ HelpDialog::HelpDialog(QWidget *parent) : QDialog(parent)
{
setWindowTitle(tr("Help"));
resize(1000, 600);
setModal(false);
setAttribute(Qt::WA_DeleteOnClose, true);
QVBoxLayout *layout = new QVBoxLayout(this);
QTextEdit *helpText = new QTextEdit(this);
View File
+79 -13
View File
@@ -10,7 +10,11 @@
#include <QMessageBox>
#include <QDesktopServices>
#include <QInputDialog>
#include <QChart>
#include <QChartView>
#include <QLineSeries>
#include "scriptengine.h"
#include "chartgraph.h"
#ifdef Q_OS_LINUX
#include <QCloseEvent>
@@ -96,6 +100,7 @@ BatchProcessing::BatchProcessing(Database *database, QWidget *parent) : QDialog(
connect(_ui->addFilesButton, &QPushButton::released, this, &BatchProcessing::addFiles);
connect(_ui->addDirButton, &QPushButton::released, this, &BatchProcessing::addDir);
connect(_ui->addMarkedButton, &QPushButton::released, this, &BatchProcessing::addMarked);
connect(_ui->removeButton, &QPushButton::released, this, &BatchProcessing::removePath);
connect(_ui->removeAllButton, &QPushButton::released, this, &BatchProcessing::removeAllPaths);
connect(_ui->startButton, &QPushButton::released, this, &BatchProcessing::runScript);
@@ -160,6 +165,17 @@ void BatchProcessing::addDir()
}
}
void BatchProcessing::addMarked()
{
QStringList files = _database->getMarkedFiles();
for(const QString &file : files)
{
QFileInfo info(file);
if(info.exists() && info.isReadable())
_ui->pathsList->addItem(file);
};
}
void BatchProcessing::removePath()
{
for(auto &item : _ui->pathsList->selectedItems())
@@ -180,19 +196,7 @@ void BatchProcessing::browse()
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
openDir(_scriptBasePath);
}
void BatchProcessing::runScript()
@@ -279,3 +283,65 @@ QJSValue BatchProcessing::getItem(const QStringList &items, const QString &label
QString ret = QInputDialog::getItem(this, tr("Select item"), label, items, current, false, &ok);
return ok ? ret : QJSValue();
}
QJSValue BatchProcessing::question(const QString &question, const QStringList &buttons, const QString &title)
{
QMessageBox::StandardButtons standardButtons = QMessageBox::NoButton;
if(buttons.contains("ok"))standardButtons |= QMessageBox::Ok;
if(buttons.contains("yes"))standardButtons |= QMessageBox::Yes;
if(buttons.contains("no"))standardButtons |= QMessageBox::No;
if(buttons.contains("yesall"))standardButtons |= QMessageBox::YesToAll;
if(buttons.contains("noall"))standardButtons |= QMessageBox::NoToAll;
if(buttons.contains("abort"))standardButtons |= QMessageBox::Abort;
if(buttons.contains("retry"))standardButtons |= QMessageBox::Retry;
if(buttons.contains("ignore"))standardButtons |= QMessageBox::Ignore;
if(buttons.contains("cancel"))standardButtons |= QMessageBox::Cancel;
if(buttons.contains("discard"))standardButtons |= QMessageBox::Discard;
if(buttons.contains("apply"))standardButtons |= QMessageBox::Apply;
if(buttons.contains("reset"))standardButtons |= QMessageBox::Reset;
if(standardButtons == QMessageBox::NoButton)standardButtons = QMessageBox::Ok;
QMessageBox::StandardButton button = QMessageBox::question(this, title, question, standardButtons);
QJSValue ret;
switch(button)
{
default:
case QMessageBox::Ok: ret = "ok"; break;
case QMessageBox::Yes: ret = "yes"; break;
case QMessageBox::No: ret = "no"; break;
case QMessageBox::YesToAll: ret = "yesall"; break;
case QMessageBox::NoToAll: ret = "noall"; break;
case QMessageBox::Abort: ret = "abort"; break;
case QMessageBox::Retry: ret = "retry"; break;
case QMessageBox::Ignore: ret = "ignore"; break;
case QMessageBox::Cancel: ret = "cancel"; break;
case QMessageBox::Discard: ret = "discard"; break;
case QMessageBox::Apply: ret = "apply"; break;
case QMessageBox::Reset: ret = "reset"; break;
}
return ret;
}
void BatchProcessing::plot(const QVariant &graph)
{
ChartGraph *chart = new ChartGraph(this);
chart->plot(graph);
}
void openDir(const QString &path)
{
#ifdef Q_OS_LINUX
QDBusConnection con = QDBusConnection::sessionBus();
QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.FileManager1", "/org/freedesktop/FileManager1", "org.freedesktop.FileManager1", "ShowFolders");
QList<QVariant> args = {QStringList(QUrl::fromLocalFile(path).toString()), QString()};
message.setArguments(args);
con.call(message);
#endif
#ifdef Q_OS_WINDOWS
QProcess::startDetached("explorer.exe", {QDir::toNativeSeparators(path)});
#endif
#ifdef Q_OS_MACOS
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
#endif
}
@@ -28,6 +28,7 @@ protected:
public slots:
void addFiles();
void addDir();
void addMarked();
void removePath();
void removeAllPaths();
void browse();
@@ -41,6 +42,11 @@ public slots:
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);
QJSValue question(const QString &question, const QStringList &buttons, const QString &title = "");
void plot(const QVariant &graph);
};
void openDir(const QString &path);
#endif // BATCHPROCESSING_H
@@ -52,6 +52,13 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addMarkedButton">
<property name="text">
<string>Add marked</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeButton">
<property name="text">
+298
View File
@@ -0,0 +1,298 @@
#include "chartgraph.h"
#include <QChartView>
#include <QVBoxLayout>
#include <QLineSeries>
#include <QBarSeries>
#include <QBarSet>
#include <QBarCategoryAxis>
#include <QScatterSeries>
#include <QMenu>
#include <QMenuBar>
#include <QValueAxis>
#include <QFileDialog>
#include <QSettings>
#include <QToolBar>
#include <QStyle>
class ChartView : public QChartView
{
QPointF _mousePos;
bool _scroll = false;
public:
ChartView(QWidget *parent) : QChartView(parent)
{
}
protected:
void keyPressEvent(QKeyEvent *event) override
{
if(!chart()->isZoomed())chart()->zoom(0.999999);//workaround so zoomReset() reset scroll
switch(event->key())
{
case Qt::Key_Plus:
chart()->zoomIn();
break;
case Qt::Key_Minus:
chart()->zoomOut();
break;
case Qt::Key_Left:
chart()->scroll(-10, 0);
break;
case Qt::Key_Right:
chart()->scroll(10, 0);
break;
case Qt::Key_Up:
chart()->scroll(0, 10);
break;
case Qt::Key_Down:
chart()->scroll(0, -10);
break;
default:
QGraphicsView::keyPressEvent(event);
break;
}
}
void mousePressEvent(QMouseEvent *event) override
{
if(event->button() == Qt::LeftButton)
{
_scroll = true;
if(!chart()->isZoomed())chart()->zoom(0.999999);//workaround so zoomReset() reset scroll
_mousePos = event->position();
}
QChartView::mousePressEvent(event);
}
void mouseMoveEvent(QMouseEvent *event) override
{
if(_scroll)
{
QPointF pos = event->position();
chart()->scroll(_mousePos.x() - pos.x(), pos.y() - _mousePos.y());
_mousePos = pos;
}
QChartView::mouseMoveEvent(event);
}
void mouseReleaseEvent(QMouseEvent *event) override
{
_scroll = false;
QChartView::mouseReleaseEvent(event);
}
void wheelEvent(QWheelEvent *event) override
{
if(event->angleDelta().y() > 0)
chart()->zoomIn();
if(event->angleDelta().y() < 0)
chart()->zoomOut();
}
};
ChartGraph::ChartGraph(QWidget *parent) : QMainWindow(parent)
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle(tr("Chart"));
_chartView = new ChartView(this);
setCentralWidget(_chartView);
_chart = new QChart;
_chartView->setChart(_chart);
_chartView->setRenderHint(QPainter::Antialiasing);
resize(1024, 768);
menuBar()->addAction(tr("Save"), this, &ChartGraph::save);
menuBar()->addAction(tr("Reset view"), [this](){ _chart->zoomReset(); });
}
void ChartGraph::plot(const QVariant &graph)
{
QVariantMap map = graph.toMap();
_chart->setTitle(map["title"].toString());
if(map.contains("legend"))
{
QVariantMap legend = map["legend"].toMap();
if(legend.contains("visible"))
_chart->legend()->setVisible(legend["visible"].toBool());
QString align = legend["align"].toString();
if(align == "top")
_chart->legend()->setAlignment(Qt::AlignTop);
else if(align == "left")
_chart->legend()->setAlignment(Qt::AlignLeft);
else if(align == "bottom")
_chart->legend()->setAlignment(Qt::AlignBottom);
else if(align == "right")
_chart->legend()->setAlignment(Qt::AlignRight);
}
QBarSeries *barSeries = nullptr;
qreal minX = INFINITY;
qreal maxX = -INFINITY;
qreal minY = INFINITY;
qreal maxY = -INFINITY;
qreal minY2 = INFINITY;
qreal maxY2 = -INFINITY;
QValueAxis *xaxis = new QValueAxis(_chart);
QBarCategoryAxis *barxaxis = new QBarCategoryAxis(_chart);
QValueAxis *yaxis = new QValueAxis(_chart);
QValueAxis *y2axis = new QValueAxis(_chart);
_chart->addAxis(xaxis, Qt::AlignBottom);
_chart->addAxis(yaxis, Qt::AlignLeft);
_chart->addAxis(y2axis, Qt::AlignRight);
_chart->addAxis(barxaxis, Qt::AlignBottom);
y2axis->setGridLinePen(Qt::DashDotLine);
for(auto s : map["series"].toList())
{
QVariantMap serie = s.toMap();
QString type = serie["type"].toString();
bool y2 = serie["y2"].toBool();
if(type == "line" || type == "points" || type == "linePoints" || type.isEmpty())
{
QXYSeries *series = nullptr;
if(type == "points")
{
QScatterSeries *scatter = new QScatterSeries(_chart);
series = scatter;
QString shape = serie["shape"].toString();
if(shape == "circle")scatter->setMarkerShape(QScatterSeries::MarkerShapeCircle);
else if(shape == "rectangle")scatter->setMarkerShape(QScatterSeries::MarkerShapeRectangle);
else if(shape == "triangle")scatter->setMarkerShape(QScatterSeries::MarkerShapeTriangle);
else if(shape == "star")scatter->setMarkerShape(QScatterSeries::MarkerShapeStar);
else if(shape == "pentagon")scatter->setMarkerShape(QScatterSeries::MarkerShapePentagon);
}
else
{
series = new QLineSeries(_chart);
}
series->setName(serie["title"].toString());
QVariantList x = serie["x"].toList();
QVariantList y = serie["y"].toList();
if(x.isEmpty())
{
for(int i = 0; i < y.size(); i++)
{
qreal val = y[i].toDouble();
if(y2)
{
minY2 = std::min(minY2, val);
maxY2 = std::max(maxY2, val);
}
else
{
minY = std::min(minY, val);
maxY = std::max(maxY, val);
}
series->append(i, val);
}
minX = std::min(minX, 0.0);
maxX = std::max(maxX, y.size() - 1.0);
}
else
{
int size = std::min(x.size(), y.size());
for(int i = 0; i < size; i++)
{
qreal val = y[i].toDouble();
if(y2)
{
minY2 = std::min(minY2, val);
maxY2 = std::max(maxY2, val);
}
else
{
minY = std::min(minY, val);
maxY = std::max(maxY, val);
}
minX = std::min(minX, x[i].toDouble());
maxX = std::max(maxX, x[i].toDouble());
series->append(x[i].toDouble(), val);
}
}
_chart->addSeries(series);
series->attachAxis(xaxis);
series->attachAxis(y2 ? y2axis : yaxis);
if(serie.contains("color"))
{
QString color = serie["color"].toString();
if(QColor::isValidColorName(color))series->setColor(QColor::fromString(color));
}
if(serie["bestFit"].toBool())
{
series->setBestFitLineVisible(true);
QPen pen = series->bestFitLinePen();
pen.setColor(series->color());
pen.setStyle(Qt::DashLine);
series->setBestFitLinePen(pen);
}
if(type == "linePoints")
series->setPointsVisible(true);
}
else if(type == "bar")
{
if(!barSeries)
{
barSeries = new QBarSeries(_chart);
_chart->addSeries(barSeries);
barSeries->attachAxis(yaxis);
barSeries->attachAxis(barxaxis);
}
QBarSet *set = new QBarSet(serie["title"].toString());
QVariantList y = serie["y"].toList();
for(int i = 0; i < y.size(); i++)
{
qreal val = y[i].toDouble();
minY = std::min(minY, val);
maxY = std::max(maxY, val);
set->append(val);
}
barSeries->append(set);
for(int i = barxaxis->count() + 1; i <= y.size(); i++)
barxaxis->append(QString::number(i));
if(serie.contains("color"))
{
QString color = serie["color"].toString();
if(QColor::isValidColorName(color))set->setColor(QColor::fromString(color));
}
}
}
if(barSeries)
{
xaxis->setRange(std::min(minX, -0.5), std::max(maxX, barxaxis->count() - 0.5));
minY = std::min(minY, 0.0);
}
else
{
xaxis->setRange(minX, maxX);
}
yaxis->setRange(minY, maxY);
y2axis->setRange(minY2, maxY2);
show();
}
void ChartGraph::save()
{
QSettings settings;
QString dir = settings.value("mainwindow/lastdir").toString();
QString output = QFileDialog::getSaveFileName(this, tr("Save as"), dir, "PNG (*.png)");
if(!output.isEmpty())
{
QPixmap graph = _chartView->grab();
graph.toImage().save(output);
}
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef CHARTGRAPH_H
#define CHARTGRAPH_H
#include <QMainWindow>
#include <QJSValue>
#include <QChart>
class ChartView;
class ChartGraph : public QMainWindow
{
Q_OBJECT
QChart *_chart;
ChartView *_chartView;
public:
explicit ChartGraph(QWidget *parent = nullptr);
void plot(const QVariant &graph);
public slots:
void save();
};
#endif // CHARTGRAPH_H
+55 -2
View File
@@ -16,10 +16,29 @@ bool Database::init(const QLatin1String &connectionName)
QDir dir(path);
database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
ngc = QSqlDatabase::addDatabase("QSQLITE", connectionName + "ngc");
if(!dir.mkpath("."))
return false;
if(ngc.isValid())
{
QString ngcDb = dir.absoluteFilePath("ngc.db");
if(!QFile::exists(ngcDb))
QFile::copy(":/ngc.db", ngcDb);
ngc.setDatabaseName(ngcDb);
if(ngc.open())
{
m_getNgc = QSqlQuery(ngc);
m_getNgc.prepare("SELECT *,IIF(V_Mag IS NULL, B_Mag, V_Mag) AS mag FROM ngc WHERE RA_deg BETWEEN ? AND ? AND DEC_deg BETWEEN ? AND ?");
}
else
{
qDebug() << "Could not open NGC database";
}
}
if(database.isValid())
{
database.setDatabaseName(dir.absoluteFilePath("database2.db"));
@@ -182,7 +201,6 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
QStringList scannedDirs;
int count = countFiles(dir, scannedDirs);
progress->setMaximum(count);
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
scannedDirs.clear();
@@ -200,7 +218,6 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
void Database::reindex(QProgressDialog *progress)
{
QVariantList deleteids;
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
QSqlQuery size("SELECT COUNT(*) FROM fits_files", database);
size.next();
@@ -239,6 +256,42 @@ QStringList Database::getFitsKeywords()
return keywords;
}
QVector<SkyObject> Database::getObjects(double minRa, double maxRa, double minDec, double maxDec)
{
QVector<SkyObject> objects;
if(!ngc.isOpen())return objects;
m_getNgc.bindValue(0, minRa);
m_getNgc.bindValue(1, maxRa);
m_getNgc.bindValue(2, minDec);
m_getNgc.bindValue(3, maxDec);
if(m_getNgc.exec())
{
while(m_getNgc.next())
{
QString name;
QString m = m_getNgc.value("M").toString();
QString ic = m_getNgc.value("IC").toString();
if(!m.isEmpty())name = "M" + m + " ";
if(!ic.isEmpty())name += "IC" + ic + " ";
name += m_getNgc.value("Name").toString();
objects.append({
name,
m_getNgc.value("Common names").toString(),
{m_getNgc.value("RA_deg").toDouble(), m_getNgc.value("DEC_deg").toDouble()},
m_getNgc.value("MajAx").toDouble(),
m_getNgc.value("MinAx").toDouble(),
m_getNgc.value("PosAng").toDouble(),
m_getNgc.value("mag").isNull() ? NAN : m_getNgc.value("mag").toDouble(),
{0, 0},
});
}
}
return objects;
}
bool Database::indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs)
{
if(scannedDirs.contains(dir.canonicalPath()))return true;
+5
View File
@@ -6,11 +6,13 @@
#include <QSqlQuery>
#include <QDir>
#include <QProgressDialog>
#include "imageinfodata.h"
class Database : public QObject
{
Q_OBJECT
QSqlDatabase database;
QSqlDatabase ngc;
QSqlQuery m_markQuery;
QSqlQuery m_unmarkQuery;
QSqlQuery m_isMarkedQuery;
@@ -22,6 +24,8 @@ class Database : public QObject
QSqlQuery m_headerKeywords;
QSqlQuery m_deleteFile;
QSqlQuery m_getNgc;
int m_progress;
public:
explicit Database(QObject *parent = 0);
@@ -37,6 +41,7 @@ public:
void indexDir(const QDir &dir, QProgressDialog *progress);
void reindex(QProgressDialog *progress);
QStringList getFitsKeywords();
QVector<SkyObject> getObjects(double minRa, double maxRa, double minDec, double maxDec);
protected:
bool indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs);
bool indexFile(const QFileInfo &file);
+19 -2
View File
@@ -10,6 +10,7 @@
#include <QContextMenuEvent>
#include <QRegularExpression>
#include <iostream>
#include "batchprocessing.h"
const QStringList DEFAULT_COLUMNS = {"EXPTIME", "OBJECT", "RA", "DEC"};
@@ -214,6 +215,8 @@ void DatabaseTableView::contextMenuEvent(QContextMenuEvent *event)
QMenu menu;
QAction *mark = menu.addAction(tr("Mark"));
QAction *unmark = menu.addAction(tr("Unmark"));
QAction *open = menu.addAction(tr("Open"));
QAction *openDirAction = menu.addAction(tr("Open file location"));
QAction *a = menu.exec(event->globalPos());
if(a == nullptr)
@@ -225,7 +228,10 @@ void DatabaseTableView::contextMenuEvent(QContextMenuEvent *event)
emit filesMarked(indexes);
else if(a == unmark)
emit filesUnmarked(indexes);
else if(a == open)
emit openFile(indexes);
else if(a == openDirAction)
emit openDir(indexes);
}
DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent)
@@ -270,6 +276,17 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
m_database->unmark(files);
m_model->filesUnmarked(indexes);
});
connect(m_tableView, &DatabaseTableView::openFile, [this](QModelIndexList indexes){
if(indexes.size())
emit loadFile(m_model->data(indexes.front().siblingAtColumn(0)).toString());
});
connect(m_tableView, &DatabaseTableView::openDir, [this](QModelIndexList indexes){
if(indexes.size())
{
QFileInfo info(m_model->data(indexes.front().siblingAtColumn(0)).toString());
openDir(info.absolutePath());
}
});
auto addFilterItems = [](QComboBox *combobox, const QStringList &fitsKeywords)
{
@@ -308,7 +325,7 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
}
QPushButton *filterButton = new QPushButton(tr("Filter"), this);
connect(filterButton, SIGNAL(pressed()), this, SLOT(applyFilter()));
connect(filterButton, &QPushButton::pressed, this, &DataBaseView::applyFilter);
hlayout->addWidget(filterButton);
connect(m_database, &Database::databaseChanged, [this, &addFilterItems](){
+2
View File
@@ -52,6 +52,8 @@ protected:
signals:
void filesMarked(QModelIndexList indexes);
void filesUnmarked(QModelIndexList indexes);
void openFile(QModelIndexList indexes);
void openDir(QModelIndexList indexes);
};
class DataBaseView : public QWidget
View File
View File
View File
+10 -1
View File
@@ -2,6 +2,8 @@
#include <QSettings>
#include <QHeaderView>
QMap<QString, QColor> headerHighlight;
ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
{
setColumnCount(3);
@@ -25,7 +27,14 @@ 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().left(1024), record.comment});
QTreeWidgetItem *item = new QTreeWidgetItem(fitsHeader, {record.key, record.value.toString().left(1024), record.comment});
if(headerHighlight.contains(record.key))
{
QColor color = headerHighlight[record.key];
item->setBackground(0, color);
item->setBackground(1, color);
item->setBackground(2, color);
}
}
addTopLevelItem(fitsHeader);
}
View File
+170 -3
View File
@@ -4,6 +4,7 @@
#include <QRegularExpression>
#include <wcslib/wcshdr.h>
#include <wcslib/wcsfix.h>
#include "database.h"
#include "libxisf.h"
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
@@ -153,9 +154,9 @@ bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const
return false;
}
void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
bool WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
{
if(wcs == nullptr)return;
if(wcs == nullptr)return false;
minRa = 1000;
maxRa = -1000;
@@ -208,6 +209,7 @@ void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, dou
if(s.contains(scp))
minDec = -90;
}
return true;
}
double hav(double x)
@@ -266,6 +268,16 @@ QString SkyPoint::toString() const
return QString("RA: %1 DEC: %2° %3' %4\"").arg(t.toString("HH'h' mm'm' ss's'")).arg(deg, 2, 'f', 0, '0').arg(min, 2, 'f', 0, '0').arg(sec, 2, 'f', 0, '0');
}
QString SkyPoint::RAString() const
{
return toHMS(ra / 15);
}
QString SkyPoint::DECString() const
{
return toDMS(dec);
}
double SkyPoint::fromHMS(const QString &hms)
{
double deg = fromDMS(hms);
@@ -308,11 +320,21 @@ QString SkyPoint::toHMS(double decHour)
QString SkyPoint::toDMS(double deg)
{
int sign = deg < 0.0 ? -1 : 1;
deg *= sign;
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'));
return QString("%1˚ %2' %3\"").arg((int)d * sign, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
}
SkyPoint SkyPoint::operator+(const SkyPoint &p)
{
SkyPoint ret;
ret.ra = ra + p.ra;
ret.dec = dec + p.dec;
return ret;
}
SkyPointScale ImageInfoData::getCenterRaDec() const
@@ -403,3 +425,148 @@ SkyPointScale ImageInfoData::getCenterRaDec() const
}
return ret;
}
SkyPoint greatCircle(SkyPoint &p, double dist, double azm)
{
dist = dist * M_PI / 180;
azm = azm * M_PI / 180;
double dec0 = p.DEC() * M_PI / 180;
double ra0 = p.RA() * M_PI / 180;
double dec1 = std::asin(std::sin(dec0) * std::cos(dist) + std::cos(dec0) * std::sin(dist) * std::cos(azm));
double ra1 = ra0 + std::atan2(std::sin(azm) * std::sin(dist) * std::cos(dec0), std::cos(dist) - std::sin(dec0) * std::sin(dec1));
return SkyPoint(ra1 * 180 / M_PI, dec1 * 180 / M_PI);
}
SkyGrid WCSDataT::prepareGrid(uint32_t w, uint32_t h, Database *database)
{
SkyGrid skyGrid;
if(!wcs)return skyGrid;
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
QPointF a,b;
worldToPixel(SkyPoint(crVal1, crVal2), a);
worldToPixel(SkyPoint(crVal1 + 0.01, crVal2), b);
skyGrid.rot_ang = std::atan2(b.y() - a.y(), b.x() - a.x()) / M_PI * -180.0;
if(database)
{
skyGrid.objects = database->getObjects(minRa, maxRa, minDec, maxDec);
for(auto &object : skyGrid.objects)
{
QPointF p;
if(worldToPixel(object.skyPoint, p))
object.pixel = p;
QPointF majax;
worldToPixel(greatCircle(object.skyPoint, (object.min_ax + object.maj_ax) / 120.0, object.pos_ang), majax);
majax -= p;
object.maj_ax = std::sqrt(QPointF::dotProduct(majax, majax));
}
}
double raStep = 15;
double decStep = 15;
double raRange = maxRa - minRa;
double decRange = maxDec - minDec;
const QVector<double> raSteps = {15, 5, 2.5, 1.25, 0.25, 20/240.0, 10/240.0, 5/240.0, 1/240.0};
const QVector<double> decSteps = {20, 10, 5, 2, 1, 20/60.0, 10/60.0, 5/60.0, 2/60.0, 1/60.0, 20/3600.0, 10/3600.0, 5/3600.0, 2/3600.0, 1/3600.0};
for(double ra : raSteps)
{
if(ra * 5 <= raRange)
{
raStep = ra;
break;
}
}
for(double dec : decSteps)
{
if(dec * 5 <= decRange)
{
decStep = dec;
break;
}
}
minRa -= std::fmod(minRa, raStep);
minDec -= std::fmod(minDec, decStep);
if(minRa < 0)minRa -= raStep;
if(minDec < 0)minDec -= decStep;
QRectF clip(0, 0, w, h);
const double step = 0.2;
maxRa += raStep;
maxDec += decStep;
for(double ra = minRa; ra <= maxRa; ra += raStep)
{
QPointF p;
worldToPixel(SkyPoint(ra, minDec), p);
skyGrid.grid.moveTo(p);
for(double dec = minDec + decStep * step; dec <= maxDec; dec += decStep * step)
{
worldToPixel(SkyPoint(ra, dec), p);
skyGrid.grid.lineTo(p);
}
}
for(double dec = minDec; dec <= maxDec; dec += decStep)
{
QPointF p;
worldToPixel(SkyPoint(minRa, dec), p);
skyGrid.grid.moveTo(p);
for(double ra = minRa + raStep * step; ra <= maxRa; ra += raStep * step)
{
worldToPixel(SkyPoint(ra, dec), p);
skyGrid.grid.lineTo(p);
}
}
SkyPoint sp1, sp2,orig;
pixelToWorld(QPointF(-1, -1), orig);
sp1 = orig;
for(uint32_t x = 0; x < w; x++)
{
QPointF p(x, 0);
if(!pixelToWorld(p, sp2))
break;
if(static_cast<int>(sp1.RA() / raStep) != static_cast<int>(sp2.RA() / raStep))
skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()});
if(static_cast<int>(sp1.DEC() / decStep) != static_cast<int>(sp2.DEC() / decStep))
skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()});
sp1 = sp2;
}
sp1 = orig;
for(uint32_t y = 0; y < h; y++)
{
QPointF p(0, y);
if(!pixelToWorld(p, sp2))
break;
if(static_cast<int>(sp1.RA() / raStep) != static_cast<int>(sp2.RA() / raStep))
skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()});
if(static_cast<int>(sp1.DEC() / decStep) != static_cast<int>(sp2.DEC() / decStep))
skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()});
sp1 = sp2;
}
skyGrid.empty = false;
return skyGrid;
}
void SkyGrid::clear()
{
empty = true;
grid.clear();
text.clear();
objects.clear();
}
+32 -1
View File
@@ -5,12 +5,15 @@
#include <QPointF>
#include <QVector>
#include <QVariant>
#include <QPainterPath>
#include <wcslib/wcs.h>
#include <cmath>
#include <memory>
namespace LibXISF { struct FITSKeyword; struct Property; }
class Database;
struct FITSRecord
{
QByteArray key;
@@ -36,10 +39,13 @@ public:
double RAHour() const { return ra / 15.0; }
double DEC() const { return dec; }
QString toString() const;
QString RAString() const;
QString DECString() const;
static double fromHMS(const QString &hms);
static double fromDMS(const QString &dms);
static QString toHMS(double decHour);
static QString toDMS(double deg);
SkyPoint operator+(const SkyPoint &p);
};
struct SkyPointScale
@@ -51,6 +57,28 @@ struct SkyPointScale
double scaleHigh = 10000.0;
};
struct SkyObject
{
QString name;
QString name2;
SkyPoint skyPoint;
double maj_ax;
double min_ax;
double pos_ang;
double mag;
QPointF pixel;
};
struct SkyGrid
{
bool empty = true;
QPainterPath grid;
QVector<QPair<QPointF, QString>> text;
QVector<SkyObject> objects;
double rot_ang = 0;
void clear();
};
class WCSDataT
{
int nwcs = 0;
@@ -65,9 +93,10 @@ public:
~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 calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
bool valid() const { return wcs; };
SkyPointScale getRaDecScale() const;
SkyGrid prepareGrid(uint32_t w, uint32_t h, Database *database);
};
struct ImageInfoData
@@ -76,6 +105,8 @@ struct ImageInfoData
QVector<QPair<QString, QString>> info;
std::shared_ptr<WCSDataT> wcs;
SkyPointScale getCenterRaDec() const;
int index = 0;
int num = 1;
};
typedef enum
+65 -18
View File
@@ -23,13 +23,16 @@ Image::Image(const QString name, int number, ImageRingList *ringList) :
{
}
void Image::load(QThreadPool *pool)
void Image::load(int index, QThreadPool *pool)
{
if(index != m_info.index && !m_loading)
m_rawImage.reset();
if(!m_rawImage && !m_loading)
{
m_loading = true;
m_released = false;
pool->start(new LoadRunable(m_name, this, m_ringList->analyzeLevel()));
pool->start(new LoadRunable(m_name, this, m_ringList->analyzeLevel(), index));
}
if(!m_loading && m_rawImage)
emit pixmapLoaded(this);
@@ -38,7 +41,7 @@ void Image::load(QThreadPool *pool)
void Image::loadThumbnail(QThreadPool *pool)
{
if(!m_thumbnail)
pool->start(new LoadRunable(m_name, this, AnalyzeLevel::None, true));
pool->start(new LoadRunable(m_name, this, AnalyzeLevel::None, 0, true));
else
emit thumbnailLoaded(this);
}
@@ -115,7 +118,7 @@ ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter,
, m_nameFilter(nameFilter)
, m_fileSuffix(nameFilter)
{
connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
connect(&m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &ImageRingList::dirChanged);
m_nameFilter.replaceInStrings(QRegularExpression("^"), "*.");
m_loadPool = new QThreadPool(this);
m_loadPool->setThreadPriority(QThread::LowPriority);
@@ -146,6 +149,7 @@ bool ImageRingList::setDir(const QString path, const QString &currentFile, bool
if(dir.exists())
{
m_currentDir = path;
QStringList scannedDirs;
QStringList absolutePaths;
std::function<void(const QString&)> scanDir = [&](const QString &path)
@@ -174,7 +178,8 @@ bool ImageRingList::setDir(const QString path, const QString &currentFile, bool
//qDebug() << absolutePaths.size();
setFilesPrivate(absolutePaths, m_liveMode ? absolutePaths.first() : currentFile);
m_fileSystemWatcher.removePaths(m_fileSystemWatcher.directories());
if(m_fileSystemWatcher.directories().size())
m_fileSystemWatcher.removePaths(m_fileSystemWatcher.directories());
m_fileSystemWatcher.addPath(path);
return true;
}
@@ -212,6 +217,11 @@ ImagePtr ImageRingList::currentImage()
return 0;
}
QString ImageRingList::currentDir() const
{
return m_currentDir;
}
void ImageRingList::increment()
{
if(m_images.size())
@@ -223,9 +233,9 @@ void ImageRingList::increment()
(*m_firstImage)->release();
m_firstImage = increment(m_firstImage);
m_currImage = increment(m_currImage);
(*m_currImage)->load(m_loadPool);
(*m_currImage)->load(0, m_loadPool);
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load(m_loadPool);
(*m_lastImage)->load(0, m_loadPool);
}
}
@@ -240,9 +250,37 @@ void ImageRingList::decrement()
(*m_lastImage)->release();
m_firstImage = decrement(m_firstImage);
m_currImage = decrement(m_currImage);
(*m_currImage)->load(m_loadPool);
(*m_currImage)->load(0, m_loadPool);
m_lastImage = decrement(m_lastImage);
(*m_firstImage)->load(m_loadPool);
(*m_firstImage)->load(0, m_loadPool);
}
}
void ImageRingList::prevSubImage()
{
if(m_images.size())
{
if((*m_currImage)->isLoading())
return;
int index = (*m_currImage)->info().index;
int num = (*m_currImage)->info().num;
if(num > 1)
(*m_currImage)->load(index == 0 ? num - 1 : index - 1, m_loadPool);
}
}
void ImageRingList::nextSubImage()
{
if(m_images.size())
{
if((*m_currImage)->isLoading())
return;
int index = (*m_currImage)->info().index;
int num = (*m_currImage)->info().num;
if(num > 1)
(*m_currImage)->load((index + 1) % num, m_loadPool);
}
}
@@ -256,6 +294,16 @@ void ImageRingList::setMarked()
setFilesPrivate(files);
}
void ImageRingList::reloadImage()
{
if(*m_currImage)
{
int index = (*m_currImage)->info().index;
(*m_currImage)->release();
(*m_currImage)->load(index, m_loadPool);
}
}
void ImageRingList::setLiveMode(bool live)
{
m_liveMode = live;
@@ -296,7 +344,7 @@ void ImageRingList::loadFile(int row)
if(m_images.empty())
return;
(*m_currImage)->load(m_loadPool);
(*m_currImage)->load(0, m_loadPool);
m_width = DEFAULT_WIDTH<m_images.size()/2 ? DEFAULT_WIDTH : m_images.size()/2;
if(m_liveMode)
@@ -305,9 +353,9 @@ void ImageRingList::loadFile(int row)
for(int i=0; i<m_width; i++)
{
m_firstImage = decrement(m_firstImage);
(*m_firstImage)->load(m_loadPool);
(*m_firstImage)->load(0, m_loadPool);
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load(m_loadPool);
(*m_lastImage)->load(0, m_loadPool);
}
if(m_lastImage != m_firstImage)
{
@@ -431,9 +479,9 @@ void ImageRingList::setPreload(int width)
for(int i = newWidth - m_width; i>0; i--)
{
m_firstImage = decrement(m_firstImage);
(*m_firstImage)->load(m_loadPool);
(*m_firstImage)->load(0, m_loadPool);
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load(m_loadPool);
(*m_lastImage)->load(0, m_loadPool);
}
}
if(newWidth < m_width)
@@ -498,8 +546,8 @@ void ImageRingList::setFilesPrivate(const QStringList files, const QString &curr
for(const QString &file : files)
{
ImagePtr ptr = make_shared<Image>(file, i++, this);
connect(ptr.get(), SIGNAL(pixmapLoaded(Image*)), this, SLOT(imageLoaded(Image*)));
connect(ptr.get(), SIGNAL(thumbnailLoaded(Image*)), this, SIGNAL(thumbnailLoaded(Image*)));
connect(ptr.get(), &Image::pixmapLoaded, this, &ImageRingList::imageLoaded);
connect(ptr.get(), &Image::thumbnailLoaded, this, &ImageRingList::thumbnailLoaded);
m_images.append(ptr);
}
@@ -538,9 +586,8 @@ void ImageRingList::imageLoaded(Image *image)
}
}
void ImageRingList::dirChanged(QString dir)
void ImageRingList::dirChanged(QString)
{
m_currentDir = dir;
if(m_liveMode)
reloadDir();
else
+6 -2
View File
@@ -28,7 +28,7 @@ class Image : public QObject
ImageRingList *m_ringList;
public:
explicit Image(const QString name, int number, ImageRingList *ringList);
void load(QThreadPool *pool);
void load(int index, QThreadPool *pool);
void loadThumbnail(QThreadPool *pool);
void release();
QString name() const;
@@ -79,6 +79,7 @@ public:
void setFile(const QString &file);
void setFiles(QStringList files);
ImagePtr currentImage();
QString currentDir() const;
void setLiveMode(bool live);
void setCalculateStats(bool stats);
void setFindPeaks(bool findPeaks);
@@ -105,7 +106,10 @@ public slots:
void toggleSlideshow(bool start);
void increment();
void decrement();
void prevSubImage();
void nextSubImage();
void setMarked();
void reloadImage();
protected:
void setFilesPrivate(const QStringList files, const QString &currentFile = QString());
QList<ImagePtr>::iterator increment(QList<ImagePtr>::iterator iter);
@@ -117,7 +121,7 @@ signals:
void currentImageChanged(int index);
protected slots:
void imageLoaded(Image *image);
void dirChanged(QString dir);
void dirChanged(QString);
void reloadDir();
};
@@ -27,8 +27,8 @@ ImageScrollArea::ImageScrollArea(Database *database, QWidget *parent) : QWidget(
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_verticalScrollBar, &QScrollBar::valueChanged, this, &ImageScrollArea::scrollEvent);
connect(m_horizontalScrollBar, &QScrollBar::valueChanged, this, &ImageScrollArea::scrollEvent);
if(imageWidgetGL)
{
@@ -142,6 +142,11 @@ void ImageScrollArea::falseColor(bool enable)
m_imageWidget->falseColor(enable);
}
void ImageScrollArea::drawGrid(bool enable)
{
m_imageWidget->drawGrid(enable);
}
QImage ImageScrollArea::renderToImage()
{
return m_imageWidget->renderToImage();
@@ -31,6 +31,7 @@ public slots:
void invert(bool enable);
void superPixel(bool enable);
void falseColor(bool enable);
void drawGrid(bool enable);
QImage renderToImage();
protected slots:
void scrollEvent();
+52 -2
View File
@@ -79,7 +79,7 @@ ImageWidgetGL::ImageWidgetGL(Database *database, QWidget *parent) : QOpenGLWidge
m_updateTimer = new QTimer(this);
m_updateTimer->setInterval(500);
m_updateTimer->setSingleShot(true);
connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(update()));
connect(m_updateTimer, &QTimer::timeout, this, static_cast<void (QOpenGLWidget::*)()>(&ImageWidgetGL::update));
setAcceptDrops(true);
QTimer::singleShot(1000, [this](){
if(!isValid())
@@ -180,6 +180,10 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
void ImageWidgetGL::setWCS(std::shared_ptr<WCSDataT> wcs)
{
m_wcs = wcs;
m_grid.clear();
if(m_drawGrid && m_wcs)
m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
}
void ImageWidgetGL::zoom(int zoom, const QPointF &mousePos)
@@ -496,6 +500,18 @@ void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scal
painter.drawImage(0, 0, img);
}
void ImageWidgetGL::drawGrid(bool enable)
{
if(m_grid.empty && m_wcs)
m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
if(enable != m_drawGrid)
{
m_drawGrid = enable;
update();
}
}
void ImageWidgetGL::paintGL()
{
float dx = m_dx;
@@ -613,9 +629,43 @@ void ImageWidgetGL::paintGL()
m_program->setUniformValue("colormapIdx", m_colormapIdx);
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_vao->release();
if(m_drawGrid && !m_grid.empty)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setPen(QPen(Qt::yellow, 1.0 / m_scale));
QTransform tran;
tran.translate(-std::floor(dx), -std::floor(dy));
tran.scale(m_scale, m_scale);
painter.setTransform(tran);
painter.setClipRect(0, 0, m_imgWidth, m_imgHeight);
painter.drawPath(m_grid.grid);
painter.setPen(Qt::yellow);
QFont font({"Arial", "serif-sans"});
font.setPointSizeF(12 / m_scale);
painter.setFont(font);
for(auto &text : m_grid.text)
painter.drawText(QRectF(text.first, QSizeF(4000, 4000)), text.second);
painter.setPen(QPen(Qt::green, 1.0 / m_scale));
QFontMetricsF fontMetric = QFontMetricsF(font);
for(auto &object : m_grid.objects)
{
QRectF rect = fontMetric.boundingRect(object.name);
rect.moveCenter(object.pixel);
painter.setTransform(tran);
painter.drawText(rect, Qt::TextDontClip, object.name);
painter.translate(object.pixel);
painter.rotate(object.pos_ang);
painter.drawEllipse(QPointF(0, 0), object.maj_ax, object.maj_ax);
}
}
}
}
}
void ImageWidgetGL::resizeGL(int w, int h)
+5
View File
@@ -8,6 +8,7 @@
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLFunctions>
#include <QPainterPath>
#include "database.h"
#include "rawimage.h"
#include "imageinfodata.h"
@@ -37,6 +38,7 @@ public:
virtual QImage renderToImage() = 0;
virtual void thumbnailLoaded(const Image *image) = 0;
virtual void showThumbnail(bool enable) = 0;
virtual void drawGrid(bool enable) = 0;
static QImage loadColormap();
};
@@ -70,6 +72,7 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
GLuint m_debayerTex = 0;
std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSDataT> m_wcs;
SkyGrid m_grid;
int m_width, m_height;
int m_imgWidth = -1, m_imgHeight = -1;
int m_currentImg = 0;
@@ -87,6 +90,7 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
bool m_selecting = false;
bool m_sizesDirty = false;
bool m_srgb = false;
bool m_drawGrid = false;
int m_thumbnailCount = 0;
int m_maxTextureSize = 0;
int m_maxArrayLayers = 0;
@@ -116,6 +120,7 @@ public:
QImage renderToImage() override;
void thumbnailLoaded(const Image *image) override;
void showThumbnail(bool enable) override;
void drawGrid(bool enable) override;
protected:
void paintGL() override;
void resizeGL(int w, int h) override;
+40 -13
View File
@@ -83,11 +83,10 @@ int loadFITSHeader(fitsfile *file, ImageInfoData &info)
return status;
}
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar, uint32_t index)
{
fitsfile *file;
int status = 0;
int type = -1;
int num = 0;
long naxes[3] = {0};
@@ -105,16 +104,32 @@ bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
fits_get_num_hdus(file, &num, &status);
if(status)return checkError();
int hdutype;
int imgtype;
int naxis;
for(int i=1; i <= num; i++)
std::vector<int> imageIdxs;
for(int i = 1; i <= num; i++)
{
fits_movabs_hdu(file, i, IMAGE_HDU, &status);if(status)return checkError();
fits_get_hdu_type(file, &type, &status);if(status)return checkError();
fits_movabs_hdu(file, i, &hdutype, &status);if(status)return checkError();
if(hdutype == IMAGE_HDU)
{
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);if(status)return checkError();
if(naxis >= 2 && naxis <= 3)imageIdxs.push_back(i);
}
}
info.num = imageIdxs.size();
info.index = index;
if(index >= imageIdxs.size())return false;
fits_movabs_hdu(file, imageIdxs[index], &hdutype, &status);if(status)return checkError();
if(hdutype == IMAGE_HDU)
{
naxes[0] = naxes[1] = naxes[2] = 0;
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);if(status)return checkError();
fits_get_img_equivtype(file, &imgtype, &status);if(status)return checkError();
if(type == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
if(hdutype == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
{
RawImage::DataType type;
int fitstype;
@@ -133,6 +148,10 @@ bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
type = RawImage::UINT16;
fitstype = TUSHORT;
break;
case LONG_IMG:
type = RawImage::UINT32;
fitstype = TINT;
break;
case ULONG_IMG:
type = RawImage::UINT32;
fitstype = TUINT;
@@ -173,13 +192,18 @@ bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
for(size_t i=0; i<size; i++)
s[i] -= INT16_MIN;
}
else if(fitstype == TINT)
{
uint32_t *s = static_cast<uint32_t*>(img.data());
size_t size = img.size() * img.channels();
for(size_t i=0; i<size; i++)
s[i] -= INT32_MIN;
}
if(img.channels() == 1 || planar)
image = std::make_shared<RawImage>(std::move(img));
else
image = RawImage::fromPlanar(img);
break;
}
}
noload:
@@ -202,14 +226,15 @@ noload:
return true;
}
bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar)
bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar, uint32_t index)
{
try
{
LibXISF::XISFReader xisf;
xisf.open(path.toLocal8Bit().data());
const LibXISF::Image &xisfImage = xisf.getImage(0);
if(index >= (uint32_t)xisf.imagesCount())return false;
const LibXISF::Image &xisfImage = xisf.getImage(index);
auto fitskeywords = xisfImage.fitsKeywords();
for(auto fits : fitskeywords)
@@ -222,6 +247,8 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
info.fitsHeader.append(prop);
}
info.num = xisf.imagesCount();
info.index = index;
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())});
@@ -378,7 +405,7 @@ bool loadRAW(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
return true;
}
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar)
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, int index, bool planar)
{
bool ret = false;
QElapsedTimer timer;
@@ -390,12 +417,12 @@ bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImag
}
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);
ret = loadFITS(path, info, rawImage, planar, index);
qDebug() << "LoadFITS" << timer.elapsed();
}
else if(path.endsWith(".XISF", Qt::CaseInsensitive))
{
ret = loadXISF(path, info, rawImage, planar);
ret = loadXISF(path, info, rawImage, planar, index);
qDebug() << "LoadXISF" << timer.elapsed();
}
else
+1 -1
View File
@@ -9,6 +9,6 @@ class RawImage;
QString makeUNCPath(const QString &path);
bool readFITSHeader(const QString &path, ImageInfoData &info);
bool readXISFHeader(const QString &path, ImageInfoData &info);
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, bool planar = false);
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, int index, bool planar = false);
#endif // LOADIMAGE_H
+9 -7
View File
@@ -10,11 +10,12 @@
#include "loadimage.h"
#include <lcms2.h>
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) :
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, int index, bool thumbnail) :
m_file(makeUNCPath(file)),
m_receiver(receiver),
m_analyzeLevel(level),
m_thumbnail(thumbnail)
m_thumbnail(thumbnail),
m_index(index)
{
}
@@ -32,7 +33,7 @@ void LoadRunable::run()
info.info.append({QObject::tr("Filename"), finfo.fileName()});
std::shared_ptr<RawImage> rawImage;
if(!loadImage(m_file, info, rawImage))
if(!loadImage(m_file, info, rawImage, m_index))
info.info.append({QObject::tr("Error"), QObject::tr("Failed to load image")});
@@ -164,11 +165,12 @@ void writeFITSImage(fitsfile *fw, std::shared_ptr<RawImage> rawimage, ImageInfoD
double vald = record.value.toDouble(&isdouble);
int valb = record.value.toString() == "T";
long long vall = record.value.toLongLong(&isint);
if(isint)isint = vall == vald;
QByteArray str = record.value.toString().toLatin1();
if(isdouble)
fits_write_key(fw, TDOUBLE, record.key.data(), &vald, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isint)
if(isint)
fits_write_key(fw, TLONGLONG, record.key.data(), &vall, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isdouble)
fits_write_key(fw, TDOUBLE, record.key.data(), &vald, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isbool)
fits_write_key(fw, TLOGICAL, record.key.data(), &valb, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(record.key == "COMMENT")
@@ -187,7 +189,7 @@ void ConvertRunable::run()
ImageInfoData imageinfo;
std::shared_ptr<RawImage> rawimage;
loadImage(m_infile, imageinfo, rawimage);
loadImage(m_infile, imageinfo, rawimage, 0);
QFileInfo info(m_outfile);
info.dir().mkpath(".");
+2 -1
View File
@@ -15,8 +15,9 @@ class LoadRunable : public QRunnable
Image *m_receiver;
AnalyzeLevel m_analyzeLevel;
bool m_thumbnail;
int m_index = 0;
public:
LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail = false);
LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, int index, bool thumbnail = false);
void run() override;
};
+18 -1
View File
@@ -4,10 +4,27 @@
#include <QTranslator>
#include <QCommandLineParser>
#include <stdlib.h>
#include "thumbnailer/genthumbnail.h"
#include "../thumbnailer/genthumbnail.h"
#include <QTreeView>
#include <QFileSystemModel>
#include <QTimer>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QFileSystemModel model;
QTreeView treeView;
QTimer::singleShot(5000, [&model,&treeView](){
treeView.setModel(&model);
model.setRootPath("/home");
});
treeView.resize(800, 600);
treeView.show();
return app.exec();
#ifdef __linux__
setenv("LC_NUMERIC", "C", 1);
#endif
+49 -45
View File
@@ -92,10 +92,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
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);
connect(m_stretchPanel, &StretchToolbar::drawGrid, m_image, &ImageScrollArea::drawGrid);
m_ringList = new ImageRingList(m_database, nameFilter, this);
m_filesystem = new FilesystemWidget(m_ringList, this);
connect(m_filesystem, SIGNAL(fileSelected(int)), this, SLOT(loadFile(int)));
connect(m_filesystem, &FilesystemWidget::fileSelected, this, static_cast<void (MainWindow::*)(int)>(&MainWindow::loadFile));
connect(m_filesystem, &FilesystemWidget::sortChanged, m_ringList, &ImageRingList::setSort);
connect(m_filesystem, &FilesystemWidget::reverseSort, m_ringList, &ImageRingList::reverseSort);
@@ -106,7 +107,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(m_filetree, &Filetree::indexDirectory, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::indexDir));
m_databaseView = new DataBaseView(m_database, this);
connect(m_databaseView, SIGNAL(loadFile(QString)), this, SLOT(loadFile(QString)));
connect(m_databaseView, &DataBaseView::loadFile, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
#ifdef PLATESOLVER
_plateSolving = new PlateSolving(this);
@@ -114,6 +115,21 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
_plateSolving->hide();
#endif
QToolBar *navigationToolbar = new QToolBar(tr("Navigation toolbar"), this);
navigationToolbar->setObjectName("navigationtoolbar");
navigationToolbar->hide();
QAction *prevAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowLeft), tr("Previous image"));
prevAction->setShortcuts({Qt::Key_Left, Qt::Key_Up});
QAction *nextAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowRight), tr("Next image"));
nextAction->setShortcuts({Qt::Key_Right, Qt::Key_Down});
QAction *prevSubAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowUp), tr("Prev sub image"), Qt::Key_PageUp);
QAction *nextSubAction = navigationToolbar->addAction(style()->standardIcon(QStyle::SP_ArrowDown), tr("Next sub image"), Qt::Key_PageDown);
connect(prevAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::decrement));
connect(nextAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::increment));
connect(prevSubAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::prevSubImage));
connect(nextSubAction, &QAction::triggered, m_ringList, static_cast<void (ImageRingList::*)()>(&ImageRingList::nextSubImage));
addToolBar(Qt::TopToolBarArea, navigationToolbar);
addToolBar(Qt::TopToolBarArea, m_stretchPanel);
QDockWidget *filesystemDock = new QDockWidget(tr("Filesystem"), this);
@@ -151,20 +167,21 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(m_ringList, &ImageRingList::pixmapLoaded, histogram, &Histogram::imageLoaded);
#ifdef PLATESOLVER
connect(m_ringList, &ImageRingList::pixmapLoaded, _plateSolving, &PlateSolving::imageLoaded);
connect(_plateSolving, &PlateSolving::headerUpdated, m_ringList, &ImageRingList::reloadImage);
#endif
connect(m_image, &ImageScrollArea::fileDropped, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
QMenu *fileMenu = new QMenu(tr("File"), this);
fileMenu->addAction(tr("Open"), QKeySequence::Open, this, SLOT(loadFile()));
fileMenu->addAction(tr("Open"), QKeySequence::Open, this, static_cast<void (MainWindow::*)()>(&MainWindow::loadFile));
fileMenu->addAction(tr("Open directory recursively"), this, &MainWindow::loadDir);
QAction *saveAs = fileMenu->addAction(tr("Save as"), QKeySequence::Save, this, SLOT(saveAs()));
QAction *saveAs = fileMenu->addAction(tr("Save as"), QKeySequence::Save, this, &MainWindow::saveAs);
fileMenu->addSeparator();
fileMenu->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("Copy marked files"), Qt::Key_F5, this, &MainWindow::copyMarked);
fileMenu->addAction(tr("Move marked files"), Qt::Key_F6, this, &MainWindow::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("Index directory"), this, static_cast<void (MainWindow::*)()>(&MainWindow::indexDir));
fileMenu->addAction(tr("Reindex files"), this, &MainWindow::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(m_database, this);
@@ -172,9 +189,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
delete batchProcessing;
});
fileMenu->addSeparator();
QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, SLOT(liveMode(bool)));
QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, &MainWindow::liveMode);
liveModeAction->setCheckable(true);
QAction *exitAction = fileMenu->addAction(tr("Exit"), this, SLOT(close()));
QAction *exitAction = fileMenu->addAction(tr("Exit"), this, &MainWindow::close);
exitAction->setShortcut(QKeySequence::Quit);
menuBar()->addMenu(fileMenu);
@@ -182,6 +199,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
editMenu->addAction(tr("Settings"), this, &MainWindow::showSettingsDialog);
menuBar()->addMenu(editMenu);
QMenu *navigationMenu = new QMenu(tr("Navigation"), this);
navigationMenu->addAction(prevAction);
navigationMenu->addAction(nextAction);
navigationMenu->addAction(prevSubAction);
navigationMenu->addAction(nextSubAction);
menuBar()->addMenu(navigationMenu);
QMenu *viewMenu = new QMenu(tr("View"), this);
viewMenu->addAction(tr("Zoom In"), QKeySequence::ZoomIn, m_image, &ImageScrollArea::zoomIn);
viewMenu->addAction(tr("Zoom Out"), QKeySequence::ZoomOut, m_image, &ImageScrollArea::zoomOut);
@@ -229,7 +253,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
settings.setValue("mainwindow/colormap", data);
});
QAction *thumbnailsAction = viewMenu->addAction(tr("Thumbnails"), Qt::Key_F2, [this](bool checked){
if(SettingsDialog::loadThumbsizes())m_ringList->clearThumbnails();
m_image->allocateThumbnails(m_ringList->imageNames());
@@ -240,14 +263,18 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
thumbnailsAction->setCheckable(true);
QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), Qt::Key_F3, m_ringList, &ImageRingList::toggleSlideshow);
slideshowAction->setCheckable(true);
viewMenu->addSeparator();
auto actionList = m_stretchPanel->actions();
actionList.removeFirst();
viewMenu->addActions(actionList);
menuBar()->addMenu(viewMenu);
QMenu *selectMenu = new QMenu(tr("Select"), this);
selectMenu->addAction(tr("Mark"), Qt::Key_F7, this, SLOT(markImage()));
selectMenu->addAction(tr("Unmark"), Qt::Key_F8, this, SLOT(unmarkImage()));
selectMenu->addAction(tr("Mark"), Qt::Key_F7, this, &MainWindow::markImage);
selectMenu->addAction(tr("Unmark"), Qt::Key_F8, this, &MainWindow::unmarkImage);
selectMenu->addSeparator();
selectMenu->addAction(tr("Mark and next"), Qt::Key_M, this, SLOT(markAndNext()));
selectMenu->addAction(tr("Unmark and next"), Qt::Key_X, this, SLOT(unmarkAndNext()));
selectMenu->addAction(tr("Mark and next"), Qt::Key_M, this, &MainWindow::markAndNext);
selectMenu->addAction(tr("Unmark and next"), Qt::Key_X, this, &MainWindow::unmarkAndNext);
selectMenu->addSeparator();
selectMenu->addAction(tr("Show marked list"), this, &MainWindow::showMarkFilesDialog);
QAction *openMarked = selectMenu->addAction(tr("Open marked"), m_ringList, &ImageRingList::setMarked);
@@ -282,6 +309,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
QMenu *dockMenu = new QMenu(tr("Docks"), this);
dockMenu->addAction(infoDock->toggleViewAction());
dockMenu->addAction(m_stretchPanel->toggleViewAction());
dockMenu->addAction(navigationToolbar->toggleViewAction());
dockMenu->addAction(filesystemDock->toggleViewAction());
dockMenu->addAction(databaseViewDock->toggleViewAction());
dockMenu->addAction(filetreeDock->toggleViewAction());
@@ -292,7 +320,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
menuBar()->addMenu(dockMenu);
QMenu *helpMenu = menuBar()->addMenu(tr("Help"));
helpMenu->addAction(tr("Help"), QKeySequence::HelpContents, [this]{ HelpDialog help(this); help.exec(); });
helpMenu->addAction(tr("Help"), QKeySequence::HelpContents, [this]{ HelpDialog *help = new HelpDialog(this); help->show(); });
helpMenu->addAction(tr("About Tenmon"), [this]{ About about(this); about.exec(); });
helpMenu->addAction(tr("About Qt"), [this](){ QMessageBox::aboutQt(this); });
helpMenu->addAction(tr("Check for update"), this, &MainWindow::checkNewVersion);
@@ -345,32 +373,6 @@ MainWindow::~MainWindow()
delete m_database;
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
switch (event->key())
{
case Qt::Key_Left:
case Qt::Key_Up:
m_ringList->decrement();
break;
case Qt::Key_Right:
case Qt::Key_Down:
m_ringList->increment();
break;
default:
event->ignore();
break;
}
if(event->isAccepted())
updateWindowTitle();
}
void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
event->ignore();
}
void MainWindow::setupSigterm()
{
#ifdef __linux__
@@ -387,7 +389,7 @@ void MainWindow::setupSigterm()
::socketpair(AF_UNIX, SOCK_STREAM, 0, socketPair);
socketNotifier = new QSocketNotifier(socketPair[1], QSocketNotifier::Read, this);
connect(socketNotifier, SIGNAL(activated(int)), this, SLOT(socketNotify()));
connect(socketNotifier, &QSocketNotifier::activated, this, &MainWindow::socketNotify);
#endif
}
@@ -811,8 +813,10 @@ void MainWindow::updateWindowTitle()
ImagePtr ptr = m_ringList->currentImage();
if(ptr)
{
QFileInfo info(ptr->name());
QString title = info.fileName();
QDir dir(m_ringList->currentDir());
QString title = dir.relativeFilePath(ptr->name());
if(ptr->info().num > 1)
title += QString(" [%1/%2]").arg(ptr->info().index + 1).arg(ptr->info().num);
if(m_database->isMarked(ptr->name()))
title += " *";
setWindowTitle(title);
-2
View File
@@ -34,8 +34,6 @@ public:
MainWindow(QWidget *parent = 0);
~MainWindow() override;
protected:
void keyPressEvent(QKeyEvent *event) override;
void keyReleaseEvent(QKeyEvent *event) override;
void setupSigterm();
static void signalHandler(int);
void closeEvent(QCloseEvent *event) override;
View File
View File
@@ -44,6 +44,7 @@ PlateSolving::PlateSolving(QWidget *parent)
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); });
connect(_solver, &Solver::headerUpdated, this, &PlateSolving::headerUpdated);
}
PlateSolving::~PlateSolving()
+2
View File
@@ -32,6 +32,8 @@ public slots:
void updateHeader();
void imageLoaded(Image *image);
void settings();
signals:
void headerUpdated(const QString &path);
private:
Ui::PlateSolving *_ui;
};
+3 -3
View File
@@ -1,8 +1,8 @@
#include "rawimage.h"
#include <cstring>
#include <lcms2.h>
#include <algorithm>
#ifndef NO_QT
#include <lcms2.h>
#include <QDebug>
#include <QElapsedTimer>
#include <QFloat16>
@@ -55,7 +55,7 @@ void RawImage::allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type)
m_width = w;
m_height = h;
m_channels = ch;
m_ch = ch == 3 ? 4 : ch;
m_ch = ch > 1 ? 4 : ch;
m_origType = m_type = type;
m_pixels = std::make_unique<PixelType[]>((size_t)m_width * m_height * m_ch * typeSize(type));
}
@@ -1071,7 +1071,6 @@ void RawImage::setICCProfile(const QByteArray &icc)
if(icc.size())
m_iccProfile = std::vector<uint8_t>(icc.begin(), icc.end());
}
#endif
void RawImage::setICCProfile(const LibXISF::ByteArray &icc)
{
@@ -1180,6 +1179,7 @@ void RawImage::generateLUT()
cmsCloseProfile(inProfile);
cmsCloseProfile(outProfile);
}
#endif
void RawImage::applySTF(const MTFParam &mtfParams)
{
+1 -1
View File
@@ -125,10 +125,10 @@ public:
bool valid() const;
#ifndef NO_QT
void setICCProfile(const QByteArray &icc);
#endif
void setICCProfile(const LibXISF::ByteArray &icc);
void convertTosRGB();
void generateLUT();
#endif
void applySTF(const MTFParam &mtfParams);
MTFParam calcMTFParams(bool linked = false, bool debayer = false) const;
const std::vector<uint16_t>& getLUT() const;
+165 -3
View File
@@ -26,7 +26,9 @@ ScriptEngine::ScriptEngine(Database *database, BatchProcessing *parent)
QJSValue core = _jsEngine->newQObject(this);
_jsEngine->globalObject().setProperty("core", core);
QJSValue fitsRecordObject = _jsEngine->newQMetaObject(&FITSRecordModify::staticMetaObject);
QJSValue textFile = _jsEngine->newQMetaObject(&TextFile::staticMetaObject);
_jsEngine->globalObject().setProperty("FITSRecordModify", fitsRecordObject);
_jsEngine->globalObject().setProperty("TextFile", textFile);
_semaphore.release(_pool->maxThreadCount());
_pool->setThreadPriority(QThread::LowPriority);
@@ -90,6 +92,55 @@ bool ScriptEngine::isMarked(const File *file)
return ret;
}
QJSValue ScriptEngine::getObjects(double ra, double dec, double distance)
{
QVector<SkyObject> objects;
QMetaObject::invokeMethod(_database, [this, ra, dec, distance](){
return _database->getObjects(ra - distance, ra + distance, dec - distance, dec + distance); }, Qt::BlockingQueuedConnection, &objects);
QJSValue ret = newArray(objects.size());
qint32 i = 0;
for(auto &object : objects)
{
QJSValue jsObj = newObject();
jsObj.setProperty("name", object.name);
jsObj.setProperty("name2", object.name2);
jsObj.setProperty("ra", object.skyPoint.RA());
jsObj.setProperty("dec", object.skyPoint.DEC());
jsObj.setProperty("mag", object.mag);
ret.setProperty(i++, jsObj);
}
return ret;
}
QJSValue ScriptEngine::getObjects(const QJSValue &bounds)
{
QVector<SkyObject> objects;
double minRa = bounds.property("minRA").toNumber();
double maxRa = bounds.property("maxRA").toNumber();
double minDec = bounds.property("minDEC").toNumber();
double maxDec = bounds.property("maxDEC").toNumber();
QMetaObject::invokeMethod(_database, [this, minRa, maxRa, minDec, maxDec](){
return _database->getObjects(minRa, maxRa, minDec, maxDec); }, Qt::BlockingQueuedConnection, &objects);
QJSValue ret = newArray(objects.size());
qint32 i = 0;
for(auto &object : objects)
{
QJSValue jsObj = newObject();
jsObj.setProperty("name", object.name);
jsObj.setProperty("name2", object.name2);
jsObj.setProperty("ra", object.skyPoint.RA());
jsObj.setProperty("dec", object.skyPoint.DEC());
jsObj.setProperty("mag", object.mag);
ret.setProperty(i++, jsObj);
}
return ret;
}
void ScriptEngine::setMaxThread(int maxthread)
{
int newval = std::max(std::min(QThread::idealThreadCount(), maxthread), 1);
@@ -134,6 +185,41 @@ QJSValue ScriptEngine::getItem(const QStringList &items, const QString &label, i
return ret;
}
QJSValue ScriptEngine::question(const QString &question, const QStringList &buttons, const QString &title) const
{
QJSValue ret;
QMetaObject::invokeMethod(_parent, "question", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QJSValue, ret), Q_ARG(QString, question), Q_ARG(QStringList, buttons), Q_ARG(QString, title));
return ret;
}
void ScriptEngine::plot(const QJSValue &graph)
{
QVariant graphV = graph.toVariant(QJSValue::ConvertJSObjects);
if(graphV.isValid())
QMetaObject::invokeMethod(_parent, "plot", Qt::QueuedConnection, Q_ARG(QVariant, graphV));
else
logError("Invalid value to be plotted");
}
QJSValue ScriptEngine::openFile(const QString &fileName, const QString &mode)
{
QFileInfo info(fileName);
if(!info.isAbsolute())
info = QFileInfo(outputDir() + fileName);
TextFile *textFile = new TextFile;
if(textFile->open(info.absoluteFilePath(), mode))
{
return _jsEngine->newQObject(textFile);
}
else
{
logError("Failed to open file " + fileName);
delete textFile;
return false;
}
}
bool ScriptEngine::convert(File *file, QString &outpath, const QString &format, const QVariantMap &params, bool async)
{
QString path;
@@ -226,7 +312,7 @@ 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());
_solver->setSearchPosition(solution.property("ra").toNumber() / 15.0, solution.property("dec").toNumber());
if(solution.hasProperty("pixscale") && solution.property("pixscale").isNumber())
{
@@ -373,12 +459,14 @@ void File::loadFitsKeywords()
{
readXISFHeader(_path, info);
}
else if(suffix().toLower() == "fits" || suffix().toLower() == "fit")
else if(suffix().toLower() == "fits" || suffix().toLower() == "fit" || suffix().toLower() == "fz")
{
readFITSHeader(_path, info);
}
else return;
_wcs = info.wcs;
for(auto &record : info.fitsHeader)
{
_fitsKeywords.append(record.key);
@@ -737,7 +825,7 @@ QJSValue File::stats()
{
ImageInfoData info;
std::shared_ptr<RawImage> rawImage;
loadImage(_path, info, rawImage);
loadImage(_path, info, rawImage, 0);
rawImage->calcStats();
RawImage::Stats stats = rawImage->imageStats();
_stats = _engine->newObject();
@@ -751,6 +839,24 @@ QJSValue File::stats()
return _stats;
}
QJSValue File::calculatedBounds()
{
QJSValue ret = _engine->newObject();
loadFitsKeywords();
if(_wcs)
{
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
_wcs->calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
ret.setProperty("minRA", minRa);
ret.setProperty("maxRA", maxRa);
ret.setProperty("minDEC", minDec);
ret.setProperty("maxDEC", maxDec);
ret.setProperty("crVal1", crVal1);
ret.setProperty("crVal2", crVal2);
}
return ret;
}
#ifdef PLATESOLVER
QJSValue File::solve(bool updateHeader)
{
@@ -820,4 +926,60 @@ void FITSRecordModify::addKeyword(const QString &key, const QVariant &value, con
_update.append({key.toLatin1(), value, comment.toLatin1()});
}
bool TextFile::open(const QString &path, const QString &mode)
{
_fr.setFileName(path);
QIODevice::OpenMode openMode;
if(mode == "r")
openMode = QIODevice::ReadOnly;
else if(mode == "w")
openMode = QIODevice::WriteOnly;
else if(mode == "a")
openMode = QIODevice::WriteOnly | QIODevice::Append;
else if(mode == "r+")
openMode = QIODevice::ReadWrite | QIODevice::ExistingOnly;
else if(mode == "w+")
openMode = QIODevice::ReadWrite;
else if(mode == "a+")
openMode = QIODevice::ReadWrite | QIODevice::Append;
else
return false;
openMode |= QIODevice::Text;//always open as text
return _fr.open(openMode);
}
void TextFile::write(const QString &data)
{
_fr.write(data.toUtf8());
}
QString TextFile::read(int maxlen)
{
QByteArray data = _fr.read(maxlen);
return data;
}
QString TextFile::readLine()
{
QByteArray data = _fr.readLine();
return QString::fromUtf8(data);
}
QString TextFile::readAll()
{
QByteArray data = _fr.readAll();
return QString::fromUtf8(data);
}
bool TextFile::seek(qint64 offset)
{
return _fr.seek(offset);
}
qint64 TextFile::pos()
{
return _fr.pos();
}
}
+22 -1
View File
@@ -8,7 +8,7 @@
#include <QThreadPool>
#include <QSemaphore>
#include "database.h"
#include "imageinfo.h"
#include "imageinfodata.h"
class BatchProcessing;
class Solver;
@@ -41,12 +41,17 @@ public:
Q_INVOKABLE void mark(File *file);
Q_INVOKABLE void unmark(File *file);
Q_INVOKABLE bool isMarked(const File *file);
Q_INVOKABLE QJSValue getObjects(double ra, double dec, double distance);
Q_INVOKABLE QJSValue getObjects(const QJSValue &bounds);
Q_INVOKABLE void setMaxThread(int maxthread);
Q_INVOKABLE void 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;
Q_INVOKABLE QJSValue question(const QString &question, const QStringList &buttons = {"ok"}, const QString &title = "") const;
Q_INVOKABLE void plot(const QJSValue &pointsArray);
Q_INVOKABLE QJSValue openFile(const QString &fileName, const QString &mode = "r");
bool convert(File *file, QString &outpath, const QString &format, const QVariantMap &params, bool async);
#ifdef PLATESOLVER
Q_INVOKABLE void setSolverProfile(int index);
@@ -93,6 +98,7 @@ class File : public QObject
bool _fitsKeywordsLoaded = false;
QStringList _fitsKeywords;
QMultiHash<QString, FITSRecord> _fitsRecords;
std::shared_ptr<WCSDataT> _wcs;
void loadFitsKeywords();
bool mkpath(const QString &path) const;
QJSValue _stats;
@@ -121,6 +127,7 @@ public:
Q_INVOKABLE File* convert(const QString &outpath, const QString &format, const QVariantMap &params = QVariantMap());
Q_INVOKABLE File* convertAsync(const QString &outpath, const QString &format, const QVariantMap &params = QVariantMap());
Q_INVOKABLE QJSValue stats();
Q_INVOKABLE QJSValue calculatedBounds();
#ifdef PLATESOLVER
Q_INVOKABLE QJSValue solve(bool updateHeader = false);
Q_INVOKABLE QJSValue extractStars(bool hfr);
@@ -142,6 +149,20 @@ public:
Q_INVOKABLE void addKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
};
class TextFile : public QObject
{
Q_OBJECT
QFile _fr;
public:
bool open(const QString &path, const QString &mode);
Q_INVOKABLE void write(const QString &data);
Q_INVOKABLE QString read(int maxlen);
Q_INVOKABLE QString readLine();
Q_INVOKABLE QString readAll();
Q_INVOKABLE bool seek(qint64 offset);
Q_INVOKABLE qint64 pos();
};
}
#endif // SCRIPTENGINE_H
+65 -3
View File
@@ -10,12 +10,15 @@
#include <QMessageBox>
#include <QDir>
#include <QPushButton>
#include <QLineEdit>
#include <QColorDialog>
#include "rawimage.h"
extern int DEFAULT_WIDTH;
extern double SATURATION;
extern int FILTERING;
extern bool BESTFIT;
extern QMap<QString, QColor> headerHighlight;
class EvenNumber : public QSpinBox
{
@@ -86,6 +89,45 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
m_bestFit->setToolTip(tr("Set Best Fit zoom level when opening new image."));
m_bestFit->setChecked(BESTFIT);
m_headerHighlight = new QListWidget(this);
m_headerHighlight->setToolTip(tr("List of FITS keywords that will be highlighted in Image info"));
for(auto i = headerHighlight.begin(); i != headerHighlight.end(); i++)
{
QListWidgetItem *item = new QListWidgetItem(m_headerHighlight);
item->setText(i.key());
item->setBackground(i.value());
}
m_keyword = new QLineEdit(this);
m_keyword->setPlaceholderText(tr("FITS keyword"));
QPushButton *color = new QPushButton(this);
QPixmap pix(16, 16);
pix.fill(m_color);
color->setIcon(pix);
connect(color, &QPushButton::clicked, [this, color](){
QColor rgb = QColorDialog::getColor(m_color, this);
if(rgb.isValid())
{
QPixmap pix(16, 16);
pix.fill(rgb);
color->setIcon(pix);
m_color = rgb;
}
});
QPushButton *add = new QPushButton(tr("Add keyword highlight"), this);
connect(add, &QPushButton::clicked, [this](){
auto list = m_headerHighlight->findItems(m_keyword->text(), Qt::MatchFixedString | Qt::MatchCaseSensitive);
if(list.size())return;
QListWidgetItem *item = new QListWidgetItem(m_headerHighlight);
item->setText(m_keyword->text());
item->setBackground(m_color);
});
QPushButton *remove = new QPushButton(tr("Remove keyword highlight"), this);
connect(remove, &QPushButton::clicked, [this](){
auto list = m_headerHighlight->selectedItems();
for(auto item : list)
delete item;
});
layout->addRow(tr("Image preload count"), m_preloadImages);
layout->addRow(tr("Thumbnails size"), m_thumSize);
@@ -95,6 +137,10 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
layout->addRow(m_qualityThumbnail);
layout->addRow(m_useNativeDialog);
layout->addRow(m_bestFit);
layout->addRow(new QLabel(tr("FITS header highlight"), this));
layout->addRow(m_headerHighlight);
layout->addRow(m_keyword, color);
layout->addRow(add, remove);
#ifdef Q_OS_WIN64
QPushButton *installThumbnailer = new QPushButton(tr("Install"), this);
@@ -125,6 +171,11 @@ void SettingsDialog::loadSettings()
FILTERING = settings.value("settings/filtering", FILTERING).toInt();
QUALITY_RESIZE = settings.value("settings/qualitythumbnail", QUALITY_RESIZE).toBool();
BESTFIT = settings.value("settings/bestfit", BESTFIT).toBool();
QStringList keywords = settings.value("settings/headerhighlightkeywords").toStringList();
QStringList colors = settings.value("settings/headerhighlightcolors").toStringList();
for(int i = 0; i < std::min(keywords.size(), colors.size()); i++)
headerHighlight.insert(keywords[i], QColor::fromString(colors[i]));
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, settings.value("settings/dontusenativedialogs", false).toBool());
}
@@ -150,10 +201,10 @@ void SettingsDialog::installThumbnailer()
QProcess regsvr;
int ret = regsvr.execute("regsvr32.exe", {"/s", path});
if(ret)
{
if(ret == 0)
QMessageBox::information(this, tr("Thumbnail support"), tr("Thumbnail generation support sucessufully installed."));
else
QMessageBox::critical(this, tr("Error"), tr("Failed to register thumbnailer. %1").arg(ret));
}
#endif
}
@@ -175,4 +226,15 @@ void SettingsDialog::saveSettings()
QApplication::setAttribute(Qt::AA_DontUseNativeDialogs, m_useNativeDialog->isChecked());
if(DEFAULT_WIDTH != m_preloadImages->value())
emit preloadChanged(m_preloadImages->value());
headerHighlight.clear();
QStringList colors;
for(int i = 0; i < m_headerHighlight->count(); i++)
{
auto item = m_headerHighlight->item(i);
colors.push_back(item->background().color().name());
headerHighlight[item->text()] = item->background().color();
}
settings.setValue("settings/headerhighlightkeywords", headerHighlight.keys());
settings.setValue("settings/headerhighlightcolors", colors);
}
@@ -5,6 +5,7 @@
#include <QSpinBox>
#include <QCheckBox>
#include <QComboBox>
#include <QListWidget>
class SettingsDialog : public QDialog
{
@@ -28,6 +29,9 @@ private:
QCheckBox *m_qualityThumbnail;
QComboBox *m_filtering;
QCheckBox *m_bestFit;
QListWidget *m_headerHighlight;
QColor m_color = Qt::yellow;
QLineEdit *m_keyword;
};
#endif // SETTINGSDIALOG_H
+2 -1
View File
@@ -41,7 +41,7 @@ bool Solver::loadImage(const QString &path)
_loaded = false;
std::shared_ptr<RawImage> image;
ImageInfoData info;
if(::loadImage(path, info, image, true))
if(::loadImage(path, info, image, 0, true))
{
return loadImage(image, path);
}
@@ -188,6 +188,7 @@ bool Solver::updateHeader(QString &error)
modify.updateKeyword("EQUINOX", 2000, QByteArray("Equinox of coordinates"));
bool ret = file.modifyFITSRecords(&modify);
if(!ret)error = tr("Failed to update file header");
else emit headerUpdated(_path);
return ret;
}
+1
View File
@@ -46,6 +46,7 @@ public slots:
signals:
void solvingDone();
void extractionDone();
void headerUpdated(const QString &path);
void logOutput(const QString &log);
};
View File
View File
+78 -12
View File
@@ -12,7 +12,7 @@ static float clamp(float x)
STFSlider::STFSlider(const QColor &color, QWidget *parent) : QWidget(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setMinimumWidth(100);
setMouseTracking(true);
@@ -64,12 +64,51 @@ void STFSlider::setMTFParams(float low, float mid, float high)
update();
}
void STFSlider::orientationChanged(Qt::Orientations orientation)
{
m_orientation = orientation;
if(m_orientation == Qt::Horizontal)
{
if(m_color == Qt::white)
{
setMaximumSize(QWIDGETSIZE_MAX, 16);
setMinimumSize(16, 16);
}
else
{
setMaximumSize(QWIDGETSIZE_MAX, 10);
setMinimumSize(10, 10);
}
}
else
{
if(m_color == Qt::white)
{
setMaximumSize(16, QWIDGETSIZE_MAX);
setMinimumSize(16, 16);
}
else
{
setMaximumSize(10, QWIDGETSIZE_MAX);
setMinimumSize(10, 10);
}
}
}
void STFSlider::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QRect rect = event->rect();
qreal w = rect.width() - 1;
qreal h = rect.height();
if(m_orientation == Qt::Vertical)
{
rect = rect.transposed();
painter.rotate(90);
w = rect.width() - 1;
h = rect.height();
painter.translate(0, -h);
}
QLinearGradient gradient(rect.topLeft(), rect.topRight());
gradient.setColorAt(0, Qt::black);
for(int i=1; i<=32; i++)
@@ -93,6 +132,11 @@ void STFSlider::paintEvent(QPaintEvent *event)
{
painter.setPen(p < m_threshold ? Qt::white : Qt::black);
painter.resetTransform();
if(m_orientation == Qt::Vertical)
{
painter.rotate(90);
painter.translate(0, -h);
}
painter.translate(w*p, 0);
painter.drawPath(tick);
};
@@ -105,15 +149,26 @@ void STFSlider::paintEvent(QPaintEvent *event)
void STFSlider::mouseMoveEvent(QMouseEvent *event)
{
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);
qreal x,w;
if(m_orientation == Qt::Horizontal)
{
x = event->position().x();
w = width();
}
else
{
x = event->position().y();
w = height();
}
if(std::abs(m_blackPoint*w - x) < 5 ||
std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*w - x) < 5 ||
std::abs(m_whitePoint*w - x) < 5)
setCursor(m_orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor);
else
unsetCursor();
qreal xw = x/width();
qreal xw = x/w;
if(event->modifiers() & Qt::ShiftModifier && !m_fineTune)
{
m_fineTune = true;
@@ -154,18 +209,29 @@ void STFSlider::mouseMoveEvent(QMouseEvent *event)
void STFSlider::mousePressEvent(QMouseEvent *event)
{
const qreal x = event->position().x();
qreal x,w;
if(m_orientation == Qt::Horizontal)
{
x = event->position().x();
w = width();
}
else
{
x = event->position().y();
w = height();
}
if(event->modifiers() & Qt::ShiftModifier)
{
m_fineTune = true;
m_fineTuneX = x/width();
m_fineTuneX = x/w;
}
if(std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*width() - x) < 5)
if(std::abs((m_blackPoint + (m_whitePoint - m_blackPoint) * m_midPoint)*w - x) < 5)
m_grabbed = 1;
else if(std::abs(m_blackPoint*width() - x) < 5)
else if(std::abs(m_blackPoint*w - x) < 5)
m_grabbed = 0;
else if(std::abs(m_whitePoint*width() - x) < 5)
else if(std::abs(m_whitePoint*w - x) < 5)
m_grabbed = 2;
else
m_grabbed = -1;
+3
View File
@@ -15,12 +15,15 @@ class STFSlider : public QWidget
float m_fineTuneX;
QColor m_color;
float m_threshold;
Qt::Orientations m_orientation = Qt::Horizontal;
public:
explicit STFSlider(const QColor &color = Qt::white, QWidget *parent = nullptr);
float blackPoint() const;
float midPoint() const;
float whitePoint() const;
void setMTFParams(float low, float mid, float high);
public slots:
void orientationChanged(Qt::Orientations orientation);
signals:
void paramChanged(float blackPoint, float midPoint, float whitePoint);
protected:
+17 -16
View File
@@ -6,17 +6,6 @@
#include <QStyle>
#include "imageringlist.h"
const float BLACK_POINT_SIGMA = -2.8f;
const float MAD_TO_SIGMA = 1.4826f;
const float TARGET_BACKGROUND = 0.25f;
float MTF(float x, float m)
{
if(x < 0)return 0;
if(x > 1)return 1;
return ((m - 1) * x) / ((2 * m - 1) * x - m);
}
StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar"), parent)
{
setObjectName("stretchtoolbar");
@@ -24,16 +13,23 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
QVBoxLayout *vbox1 = new QVBoxLayout(lum);
m_stfSlider = new STFSlider(Qt::white, this);
vbox1->addWidget(m_stfSlider);
connect(this, &StretchToolbar::orientationChanged, m_stfSlider, &STFSlider::orientationChanged);
m_stfSliderR = new STFSlider(Qt::red, this);
m_stfSliderG = new STFSlider(Qt::green, this);
m_stfSliderB = new STFSlider(Qt::blue, this);
QWidget *rgb = new QWidget(this);
QVBoxLayout *vbox2 = new QVBoxLayout(rgb);
vbox2->setSpacing(0);
vbox2->addWidget(m_stfSliderR);
vbox2->addWidget(m_stfSliderG);
vbox2->addWidget(m_stfSliderB);
QBoxLayout *box2 = new QBoxLayout(orientation() == Qt::Horizontal ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight, rgb);
box2->setSpacing(0);
box2->addWidget(m_stfSliderR);
box2->addWidget(m_stfSliderG);
box2->addWidget(m_stfSliderB);
connect(this, &StretchToolbar::orientationChanged, m_stfSliderR, &STFSlider::orientationChanged);
connect(this, &StretchToolbar::orientationChanged, m_stfSliderG, &STFSlider::orientationChanged);
connect(this, &StretchToolbar::orientationChanged, m_stfSliderB, &STFSlider::orientationChanged);
connect(this, &StretchToolbar::orientationChanged, [box2](Qt::Orientations orientation){
box2->setDirection(orientation == Qt::Horizontal ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight);
});
m_stack = new QStackedWidget(this);
m_stack->addWidget(lum);
@@ -93,6 +89,11 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
m_autoStretchOnLoad = addAction(QIcon(":/nuke_a.png"), tr("Apply auto stretch on load"));
m_autoStretchOnLoad->setCheckable(true);
QAction *showGridButton = addAction(QIcon(":/grid.svg"), tr("Draw equatorial grid"));
showGridButton->setCheckable(true);
connect(showGridButton, &QAction::toggled, this, &StretchToolbar::drawGrid);
QSettings settings;
m_autoStretchOnLoad->setChecked(settings.value("stretchtoolbar/autostretch", false).toBool());
}
@@ -33,6 +33,7 @@ signals:
void invert(bool enable);
void superPixel(bool enable);
void falseColor(bool enable);
void drawGrid(bool enable);
};
#endif // STRETCHTOOLBAR_H
View File
+10 -10
View File
@@ -4,28 +4,28 @@ if(BUILD_THUMBNAILER)
if(WIN32)
add_library(tenmonthumbnailer SHARED
Dll.cpp
loadxisf.cpp
loadimage.cpp
TenmonThumbnailProvider.cpp
../rawimage.h
../rawimage.cpp
../rawimage_sse.cpp)
../src/rawimage.h
../src/rawimage.cpp
../src/rawimage_sse.cpp)
set_target_properties(tenmonthumbnailer PROPERTIES PREFIX "")
target_compile_definitions(tenmonthumbnailer PRIVATE NO_QT)
target_include_directories(tenmonthumbnailer PRIVATE ../libXISF)
target_link_libraries(tenmonthumbnailer PRIVATE shlwapi ${LCMS2_LIB} ${FITS_LIB} XISF)
target_link_libraries(tenmonthumbnailer PRIVATE shlwapi ${FITS_LIB} XISF)
target_link_options(tenmonthumbnailer PRIVATE "-static")
else(WIN32)
qt_add_executable(tenmonthumbnailer
main.cpp
../loadimage.cpp
../rawimage.cpp
../rawimage_sse.cpp
../imageinfodata.cpp)
loadimage.cpp
../src/rawimage.cpp
../src/rawimage_sse.cpp)
target_link_libraries(tenmonthumbnailer PRIVATE Qt6::Core Qt6::Gui ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
target_link_libraries(tenmonthumbnailer PRIVATE ${FITS_LIB} XISF)
target_include_directories(tenmonthumbnailer PRIVATE ../libXISF)
target_compile_definitions(tenmonthumbnailer PRIVATE NO_QT)
endif(WIN32)
endif(BUILD_THUMBNAILER)
+87 -9
View File
@@ -2,9 +2,87 @@
#include <thumbcache.h> // For IThumbnailProvider.
#include <new>
#include "libxisf.h"
#include "../src/rawimage.h"
bool loadXISF(const LibXISF::ByteArray &data, HBITMAP *hbmp, UINT thumbSize);
bool loadFITS(const LibXISF::ByteArray &data, HBITMAP *hbmp, UINT thumbSize);
bool loadXISF(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
bool loadFITS(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
void RawImageToHTBITMAP(std::shared_ptr<RawImage> &rawImage, HBITMAP *hbmp, UINT thumbSize)
{
rawImage->calcStats();
DWORD thre = 20;
DWORD dataSize = 4;
HRESULT hr = HRESULT_FROM_WIN32(RegGetValueW(HKEY_CURRENT_USER, L"SOFTWARE\\nou\\Tenmon\\settings", L"thumbnailstretchthreshold", RRF_RT_DWORD, NULL, &thre, &dataSize));
float thref = 0.1f;
if(hr == S_OK)
thref = thre / 100.0f;
if(rawImage->imageStats().m_median[0] < rawImage->norm() * thref)
{
//OutputDebugStringA("Stretch image");
MTFParam params = rawImage->calcMTFParams();
rawImage->applySTF(params);
}
UINT w = rawImage->width();
UINT h = rawImage->height();
UINT cw = thumbSize;
UINT ch = thumbSize;
if (w > h)
ch = h * thumbSize / w;
else
cw = w * thumbSize / h;
rawImage->resize(cw, ch);
rawImage->convertToType(RawImage::UINT8);
BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = cw;
bmi.bmiHeader.biHeight = -static_cast<LONG>(ch);
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
UINT lw = cw * 4;
BYTE *pBits;
HBITMAP bmp = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, reinterpret_cast<void **>(&pBits), NULL, 0);
const unsigned char *p = (const unsigned char*)rawImage->data();
const unsigned short *ps = (const unsigned short*)rawImage->data();
if(rawImage->channels() == 1)
{
for(UINT y = 0; y < ch; y++)
{
for(UINT x = 0; x < cw; x++)
{
pBits[(y * lw) + x * 4 + 0] = p[y * cw + x];
pBits[(y * lw) + x * 4 + 1] = p[y * cw + x];
pBits[(y * lw) + x * 4 + 2] = p[y * cw + x];
pBits[(y * lw) + x * 4 + 3] = 255;
}
}
}
else
{
for(UINT y = 0; y < ch; y++)
{
for(UINT x = 0; x < cw; x++)
{
pBits[(y * lw) + x * 4 + 0] = p[y * cw * 4 + x * 4 + 2];
pBits[(y * lw) + x * 4 + 1] = p[y * cw * 4 + x * 4 + 1];
pBits[(y * lw) + x * 4 + 2] = p[y * cw * 4 + x * 4 + 0];
pBits[(y * lw) + x * 4 + 3] = 255;
}
}
}
*hbmp = bmp;
}
class TenmonThumbProvider : public IInitializeWithStream,
public IThumbnailProvider
@@ -103,19 +181,19 @@ IFACEMETHODIMP TenmonThumbProvider::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_AL
*pdwAlpha = WTSAT_RGB;
data.resize(readSize);
std::shared_ptr<RawImage> rawImage;
if(data[0] == 'X' && data[1] == 'I' && data[2] == 'S' && data[3] == 'F')
{
if(loadXISF(data, phbmp, cx))
return S_OK;
else
if(!loadXISF(data, rawImage))
return E_FAIL;
}
else
{
if(loadFITS(data, phbmp, cx))
return S_OK;
else
if(!loadFITS(data, rawImage))
return E_FAIL;
}
return E_FAIL;
RawImageToHTBITMAP(rawImage, phbmp, cx);
return S_OK;
}
+3 -4
View File
@@ -1,13 +1,12 @@
#include "genthumbnail.h"
#include "../rawimage.h"
#include "../loadimage.h"
#include "../src/rawimage.h"
#include "../src/loadimage.h"
int generateThumbnail(const QString &input, const QString &output, uint32_t size)
{
ImageInfoData info;
std::shared_ptr<RawImage> rawImage;
if(!loadImage(input, info, rawImage))
if(!loadImage(input, info, rawImage, 0))
return 2;
if(!rawImage)
+163
View File
@@ -0,0 +1,163 @@
#include "libxisf.h"
#include "../src/rawimage.h"
#ifdef WIN32
#include <windows.h>
#endif
#include <fitsio2.h>
bool OpenGLES = false;
bool loadXISF(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage)
{
try
{
LibXISF::XISFReader xisf;
xisf.open(data);
const LibXISF::Image &xisfImage = xisf.getImage(0);
RawImage::DataType type;
switch(xisfImage.sampleFormat())
{
case LibXISF::Image::UInt8: type = RawImage::UINT8; break;
case LibXISF::Image::UInt16: type = RawImage::UINT16; break;
case LibXISF::Image::UInt32: type = RawImage::UINT32; break;
case LibXISF::Image::Float32: type = RawImage::FLOAT32; break;
case LibXISF::Image::Float64: type = RawImage::FLOAT64; break;
default: return false;
}
LibXISF::Image tmpImage = xisfImage;
tmpImage.convertPixelStorageTo(LibXISF::Image::Planar);
if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray)
{
rawImage = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
std::memcpy(rawImage->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
}
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
{
rawImage = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
}
return true;
}
catch (LibXISF::Error &err)
{
#ifdef WIN32
char text[1024];
sprintf_s(text, 1000, "Failed to open XISF image %s", err.what());
OutputDebugStringA(text);
#endif
return false;
}
return false;
}
bool loadFITS(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage)
{
fitsfile *file;
int status = 0;
int hdutype = -1;
int num = 0;
long naxes[3] = {0};
auto checkError = [&status]()
{
char err[100];
fits_get_errstatus(status, err);
#ifdef WIN32
char text[1000];
sprintf_s(text, 1000, "Failed to load FITS file %s", err);
OutputDebugStringA(text);
#endif
return false;
};
const void *dataPtr = data.data();
size_t size = data.size();
fits_open_memfile(&file, "file.fits", READONLY, (void**)&dataPtr, &size, 0, nullptr, &status);
if(status)return checkError();
fits_get_num_hdus(file, &num, &status);
if(status)return checkError();
int imgtype;
int naxis;
for(int i=1; i <= num; i++)
{
fits_movabs_hdu(file, i, &hdutype, &status);if(status)return checkError();
if(hdutype == IMAGE_HDU)
{
naxes[0] = naxes[1] = naxes[2] = 0;
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);if(status)return checkError();
fits_get_img_equivtype(file, &imgtype, &status);if(status)return checkError();
if(hdutype == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
{
RawImage::DataType type;
int fitstype;
long fpixel[3] = {1,1,1};
switch(imgtype)
{
case BYTE_IMG:
type = RawImage::UINT8;
fitstype = TBYTE;
break;
case SHORT_IMG:
type = RawImage::UINT16;
fitstype = TSHORT;
break;
case USHORT_IMG:
type = RawImage::UINT16;
fitstype = TUSHORT;
break;
case ULONG_IMG:
type = RawImage::UINT32;
fitstype = TUINT;
break;
case FLOAT_IMG:
type = RawImage::FLOAT32;
fitstype = TFLOAT;
break;
case DOUBLE_IMG:
type = RawImage::FLOAT64;
fitstype = TDOUBLE;
break;
default:
return false;
break;
}
size_t size = naxes[0]*naxes[1];
size_t w = naxes[0];
size_t h = naxes[1];
RawImage img(w, h, naxis == 2 ? 1 : naxes[2], type);
uint8_t *data = static_cast<uint8_t*>(img.data());
for (int i=1; i==1 || i<=naxes[2]; i++)
{
fpixel[2] = i;
fits_read_pix(file, fitstype, fpixel, size, NULL, data + img.size() * RawImage::typeSize(type) * (i-1), NULL, &status);
if(status)return checkError();
}
if(fitstype == TSHORT)
{
uint16_t *s = static_cast<uint16_t*>(img.data());
size_t size = img.size() * img.channels();
for(size_t i=0; i<size; i++)
s[i] -= INT16_MIN;
}
if(img.channels() == 1)
rawImage = std::make_shared<RawImage>(std::move(img));
else
rawImage = RawImage::fromPlanar(img);
return true;
}
}
}
return false;
}
-237
View File
@@ -1,237 +0,0 @@
#include "libxisf.h"
#include <thumbcache.h>
#include "../rawimage.h"
#include <fitsio2.h>
bool OpenGLES = false;
void RawImageToHTBITMAP(std::shared_ptr<RawImage> &rawImage, HBITMAP *hbmp, UINT thumbSize)
{
rawImage->calcStats();
DWORD thre = 10;
DWORD dataSize = 4;
//HRESULT hr = HRESULT_FROM_WIN32(RegGetValueW(HKEY_CURRENT_USER, L"SOFTWARE\\nou\\Tenmon\\settings", L"thumbnailstretchthreshold", RRF_RT_DWORD, NULL, &thre, &dataSize));
float thref = 0.1f;
/*if(hr == S_OK)
thref = thre / 100.0f;*/
if(rawImage->imageStats().m_mean[0] < rawImage->norm() * thref)
{
//OutputDebugStringA("Stretch image");
MTFParam params = rawImage->calcMTFParams();
rawImage->applySTF(params);
}
UINT w = rawImage->width();
UINT h = rawImage->height();
UINT cw = thumbSize;
UINT ch = thumbSize;
if (w > h)
ch = h * thumbSize / w;
else
cw = w * thumbSize / h;
rawImage->resize(cw, ch);
rawImage->convertToType(RawImage::UINT8);
BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = cw;
bmi.bmiHeader.biHeight = -static_cast<LONG>(ch);
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
UINT lw = cw * 4;
BYTE *pBits;
HBITMAP bmp = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, reinterpret_cast<void **>(&pBits), NULL, 0);
const unsigned char *p = (const unsigned char*)rawImage->data();
const unsigned short *ps = (const unsigned short*)rawImage->data();
if(rawImage->channels() == 1)
{
for(UINT y = 0; y < ch; y++)
{
for(UINT x = 0; x < cw; x++)
{
pBits[(y * lw) + x * 4 + 0] = p[y * cw + x];
pBits[(y * lw) + x * 4 + 1] = p[y * cw + x];
pBits[(y * lw) + x * 4 + 2] = p[y * cw + x];
pBits[(y * lw) + x * 4 + 3] = 255;
}
}
}
else
{
for(UINT y = 0; y < ch; y++)
{
for(UINT x = 0; x < cw; x++)
{
pBits[(y * lw) + x * 4 + 0] = p[y * cw * 4 + x * 4 + 2];
pBits[(y * lw) + x * 4 + 1] = p[y * cw * 4 + x * 4 + 1];
pBits[(y * lw) + x * 4 + 2] = p[y * cw * 4 + x * 4 + 0];
pBits[(y * lw) + x * 4 + 3] = 255;
}
}
}
*hbmp = bmp;
}
bool loadXISF(const LibXISF::ByteArray &data, HBITMAP *hbmp, UINT thumbSize)
{
try
{
LibXISF::XISFReader xisf;
xisf.open(data);
const LibXISF::Image &xisfImage = xisf.getImage(0);
RawImage::DataType type;
switch(xisfImage.sampleFormat())
{
case LibXISF::Image::UInt8: type = RawImage::UINT8; break;
case LibXISF::Image::UInt16: type = RawImage::UINT16; break;
case LibXISF::Image::UInt32: type = RawImage::UINT32; break;
case LibXISF::Image::Float32: type = RawImage::FLOAT32; break;
case LibXISF::Image::Float64: type = RawImage::FLOAT64; break;
default: break;
}
LibXISF::Image tmpImage = xisfImage;
tmpImage.convertPixelStorageTo(LibXISF::Image::Planar);
std::shared_ptr<RawImage> rawImage;
if(tmpImage.colorSpace() == LibXISF::Image::ColorSpace::Gray)
{
rawImage = std::make_shared<RawImage>(tmpImage.width(), tmpImage.height(), 1, type);
std::memcpy(rawImage->data(), tmpImage.imageData(), tmpImage.imageDataSize() / tmpImage.channelCount());
}
else if(tmpImage.channelCount() == 3 || tmpImage.channelCount() == 4)
{
rawImage = RawImage::fromPlanar(tmpImage.imageData(), tmpImage.width(), tmpImage.height(), tmpImage.channelCount(), type);
}
RawImageToHTBITMAP(rawImage, hbmp, thumbSize);
return true;
}
catch (LibXISF::Error &err)
{
char text[1024];
sprintf_s(text, 1000, "Failed to open XISF image %s", err.what());
OutputDebugStringA(text);
return false;
}
return false;
}
bool loadFITS(const LibXISF::ByteArray &data, HBITMAP *hbmp, UINT thumbSize)
{
fitsfile *file;
int status = 0;
int type = -1;
int num = 0;
long naxes[3] = {0};
auto checkError = [&status]()
{
char err[100];
char text[1000];
fits_get_errstatus(status, err);
sprintf_s(text, 1000, "Failed to load FITS file %s", err);
OutputDebugStringA(text);
return false;
};
const void *dataPtr = data.data();
size_t size = data.size();
fits_open_memfile(&file, "file.fits", READONLY, (void**)&dataPtr, &size, 0, nullptr, &status);
if(status)return checkError();
fits_get_num_hdus(file, &num, &status);
if(status)return checkError();
int imgtype;
int naxis;
for(int i=1; i <= num; i++)
{
fits_movabs_hdu(file, i, IMAGE_HDU, &status);if(status)return checkError();
fits_get_hdu_type(file, &type, &status);if(status)return checkError();
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);if(status)return checkError();
fits_get_img_equivtype(file, &imgtype, &status);if(status)return checkError();
if(type == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
{
RawImage::DataType type;
int fitstype;
long fpixel[3] = {1,1,1};
switch(imgtype)
{
case BYTE_IMG:
type = RawImage::UINT8;
fitstype = TBYTE;
break;
case SHORT_IMG:
type = RawImage::UINT16;
fitstype = TSHORT;
break;
case USHORT_IMG:
type = RawImage::UINT16;
fitstype = TUSHORT;
break;
case ULONG_IMG:
type = RawImage::UINT32;
fitstype = TUINT;
break;
case FLOAT_IMG:
type = RawImage::FLOAT32;
fitstype = TFLOAT;
break;
case DOUBLE_IMG:
type = RawImage::FLOAT64;
fitstype = TDOUBLE;
break;
default:
return false;
break;
}
size_t size = naxes[0]*naxes[1];
size_t w = naxes[0];
size_t h = naxes[1];
RawImage img(w, h, naxis == 2 ? 1 : naxes[2], type);
uint8_t *data = static_cast<uint8_t*>(img.data());
for (int i=1; i==1 || i<=naxes[2]; i++)
{
fpixel[2] = i;
fits_read_pix(file, fitstype, fpixel, size, NULL, data + img.size() * RawImage::typeSize(type) * (i-1), NULL, &status);
if(status)return checkError();
}
if(fitstype == TSHORT)
{
uint16_t *s = static_cast<uint16_t*>(img.data());
size_t size = img.size() * img.channels();
for(size_t i=0; i<size; i++)
s[i] -= INT16_MIN;
}
std::shared_ptr<RawImage> image;
if(img.channels() == 1)
image = std::make_shared<RawImage>(std::move(img));
else
image = RawImage::fromPlanar(img);
RawImageToHTBITMAP(image, hbmp, thumbSize);
return true;
}
}
return false;
}
+54 -36
View File
@@ -1,62 +1,80 @@
#include <QCoreApplication>
#include <QCommandLineParser>
#include "../rawimage.h"
#include "../loadimage.h"
#include <vector>
#include <string>
#include <iostream>
#include "../src/rawimage.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
bool OpenGLES = false;
bool loadXISF(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
bool loadFITS(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
std::vector<std::string> args;
for(int i=0; i<argc; i++)
args.push_back(argv[i]);
QCommandLineParser parser;
parser.addOption({{"s", "size"}, "Size of the thumbnail in pixels (default: 128)", "size", "128"});
parser.addPositionalArgument("input", "Input image file");
parser.addPositionalArgument("output", "Output image file");
parser.addHelpOption();
parser.process(a);
QStringList args = parser.positionalArguments();
if(args.size() < 2)
if(args.size() < 3)
return 1;
QString input = args[0];
QString output = args[1];
std::string input = args[1];
std::string output = args[2];
ImageInfoData info;
std::shared_ptr<RawImage> rawImage;
if(!loadImage(input, info, rawImage))
LibXISF::ByteArray data;
std::ifstream fr;
fr.open(input, std::ios_base::in | std::ios_base::binary);
if(!fr.is_open())
return 2;
if(!rawImage)
fr.seekg(0, std::ios_base::end);
size_t len = fr.tellg();
fr.seekg(0, std::ios_base::beg);
data.resize(len);
fr.read(data.data(), len);
if(fr.bad())
return 3;
bool ok;
int size = parser.value("s").toInt(&ok);
if(!ok)
size = 128;
if(input.find(".xisf") != std::string::npos)
{
if(!loadXISF(data, rawImage))
return 4;
}
else
{
if(!loadFITS(data, rawImage))
return 4;
}
if(!rawImage)
return 5;
uint32_t thumbSize = 256;
uint32_t w = rawImage->width();
uint32_t h = rawImage->height();
uint32_t cw = thumbSize;
uint32_t ch = thumbSize;
if (w > h)
ch = h * thumbSize / w;
else
cw = w * thumbSize / h;
QSize rect(rawImage->width(), rawImage->height());
rect.scale(size, size, Qt::KeepAspectRatio);
rawImage->calcStats();
rawImage->resize(rect.width(), rect.height());
if(rawImage->imageStats().m_median[0] < rawImage->norm() * 0.2f)
rawImage->resize(cw, ch);
if(rawImage->imageStats().m_median[0] < rawImage->norm() * 0.1f)
{
MTFParam mtfParams = rawImage->calcMTFParams(true);
rawImage->applySTF(mtfParams);
}
rawImage->convertToType(RawImage::UINT8);
QImage img;
if(rawImage->channels() == 1)
img = QImage((const uchar*)rawImage->data(), rawImage->width(), rawImage->height(), rawImage->widthBytes(), QImage::Format_Grayscale8);
stbi_write_png(output.c_str(), cw, ch, 1, rawImage->data(), rawImage->widthBytes());
else
img = QImage((const uchar*)rawImage->data(), rawImage->width(), rawImage->height(), rawImage->widthBytes(), QImage::Format_RGBA8888);
if(!img.save(output, "png"))
return 4;
stbi_write_png(output.c_str(), cw, ch, 4, rawImage->data(), rawImage->widthBytes());
return 0;
}
File diff suppressed because it is too large Load Diff
Binary file not shown.
+366 -162
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+382 -178
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
+367 -163
View File
File diff suppressed because it is too large Load Diff