Compare commits

..

30 Commits

Author SHA1 Message Date
nou b6ae7d4cdb Update french help, fix few typo. Thanks to Patrick Chevalley 2022-06-24 21:14:08 +02:00
nou 1bd48e8fb4 Refres database table when indexing is done 2022-06-24 18:30:28 +02:00
nou 1d65eda490 Search with CRVAL# 2022-06-24 18:06:26 +02:00
nou 97346df596 Update help 2022-06-24 18:05:33 +02:00
nou a157b274a2 Save to database CRVALi 2022-06-23 16:57:00 +02:00
nou 6466702819 Set correct type to vertex attribute 2022-06-22 23:43:56 +02:00
nou b4ea65b42a Upload sizes to OpenGL only once per draw
Should fix MacOS issue
2022-06-22 23:24:29 +02:00
nou 1682de4e1b Update translations 2022-06-22 23:01:35 +02:00
nou 00872b31df Add icon for MacOS 2022-06-21 09:12:26 +02:00
nou 2884787916 Changes to build on MacOS 2022-06-20 23:52:09 +02:00
nou 86ea9fc137 Add MacOS PCL libs 2022-06-20 23:36:31 +02:00
nou 67199a033d Better status bar 2022-06-17 13:24:52 +02:00
nou 0f182900c2 Fix build on older gcc 2022-06-17 09:54:50 +02:00
nou 19ed5ae1a4 Better handling of FITS records 2022-06-17 00:31:27 +02:00
nou 46215c7a7d Workaround for incorect handling of PV1_2 2022-06-16 23:46:54 +02:00
nou c346487504 Add parsing WCS info from XISF 2022-06-16 23:44:28 +02:00
nou 5b6fead6f1 Fix issue with numeric values in XISF FITS header 2022-06-15 08:55:44 +02:00
nou 3e94aa0eda Add search by RA and DEC point 2022-06-14 22:46:40 +02:00
nou 08e70cdb52 Calculate bounds on indexing 2022-06-14 21:44:32 +02:00
nou 04e587b51c Add WCSData::calculateBounds 2022-06-13 23:28:39 +02:00
nou 42dd55244a Add wcslib as lib name 2022-06-13 18:50:57 +02:00
nou 6b9ea5e4b9 Add support for WCS 2022-06-13 18:02:58 +02:00
nou 701a425cc7 Add support for 16 bit PNG images 2022-06-11 22:39:06 +02:00
nou 9cd2ae14b3 Add status bar with color value 2022-06-11 17:19:03 +02:00
nou f7e4e1874f Proper filter setting 2022-06-11 14:10:07 +02:00
nou e6749fc487 Move shaders to subdirectory 2022-06-11 12:27:15 +02:00
nou dc6aa6baa8 Fix bug with wayland backend 2022-06-09 23:30:23 +02:00
nou c8a70d22f8 Show marked files in file list bold 2022-06-04 15:59:51 +02:00
nou dbb533176c Remove unecessary call 2022-06-04 08:10:47 +02:00
nou 032f5b0577 Improve file selection in file system widget 2022-06-03 10:51:51 +02:00
39 changed files with 897 additions and 111 deletions
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+20 -6
View File
@@ -19,6 +19,7 @@ find_library(GSLCBLAS_LIB gslcblas REQUIRED)
find_library(EXIF_LIB exif REQUIRED)
find_library(FITS_LIB cfitsio REQUIRED)
find_library(RAW_LIB NAMES raw_r REQUIRED)
find_library(WCS_LIB wcs wcslib PATHS REQUIRED)
set(TENMON_SRC
about.cpp
@@ -35,6 +36,7 @@ set(TENMON_SRC
markedfiles.cpp
rawimage.cpp
starfit.cpp
statusbar.cpp
stfslider.cpp
stretchtoolbar.cpp
)
@@ -43,31 +45,43 @@ qt5_add_resources(TENMON_SRC resources.qrc)
if(WIN32)
list(APPEND TENMON_SRC icon.rc)
add_compile_definitions("__PCL_WINDOWS")
set(tenmon_ICON "")
elseif(APPLE)
add_compile_definitions("__PCL_MACOS")
set(tenmon_ICON ${CMAKE_CURRENT_SOURCE_DIR}/tenmon.icns)
set_source_files_properties(${tenmon_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
else()
add_compile_definitions("__PCL_LINUX")
set(tenmon_ICON "")
endif()
add_executable(tenmon WIN32 ${TENMON_SRC})
add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC})
find_path(FITS_INCLUDE fitsio2.h PATH_SUFFIXES cfitsio REQUIRED)
target_include_directories(tenmon PRIVATE ${OpenCV_INCLUDE_DIRS} ${FITS_INCLUDE} 3rdparty/include ${CMAKE_BINARY_DIR})
if(WIN32)
target_link_directories(tenmon PRIVATE 3rdparty/lib/Windows)
elseif(APPLE)
target_link_directories(tenmon PRIVATE 3rdparty/lib/MacOS)
else()
target_link_directories(tenmon PRIVATE 3rdparty/lib/Linux)
endif()
target_link_libraries(tenmon Qt5::Widgets Qt5::Sql ${OpenCV_LIBS} ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB})
target_link_libraries(tenmon PCL lcms lz4 RFC6234 zlib)
target_link_libraries(tenmon Qt5::Widgets Qt5::Sql ${OpenCV_LIBS} ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB})
if(APPLE)
target_link_libraries(tenmon PCL-pxi lcms-pxi lz4-pxi RFC6234-pxi zlib-pxi "-framework CoreFoundation")
else()
target_link_libraries(tenmon PCL lcms lz4 RFC6234 zlib)
endif(APPLE)
if(LIBRAW_STATIC)
add_compile_definitions("LIBRAW_NODLL")
target_link_libraries(tenmon jasper)
endif()
install(TARGETS tenmon)
if(UNIX)
install(TARGETS tenmon BUNDLE DESTINATION .)
if(UNIX AND NOT APPLE)
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
if(XDG-DESKTOP-MENU_EXECUTABLE)
install(SCRIPT install.cmake)
@@ -80,7 +94,7 @@ if(UNIX)
install(FILES org.nou.tenmon.png DESTINATION "/usr/share/icons/hicolor/32x32/apps")
endif()
endif()
endif(UNIX)
endif(UNIX AND NOT APPLE)
option(RELEASE_BUILD "Release build" OFF)
if(RELEASE_BUILD)
+22 -1
View File
@@ -24,6 +24,8 @@ The <i>File system</i> panel shows other images in the same directory as the loa
<i>Stretch panel</i> containing various options for auto-stretching linear image data.</p>
<p>All panels in the interface can be moved around and/or closed. Any closed or non-visible panel can be
re-opened through the <i>Docks</i> menu at the top.</p>
<p>At bottom there is status bar that show current lightness or red, green, blue pixel value under mouse cursor then X and Y coordinates and
if image contain World Coordinate System metadata it show celestial coordinates.</p>
<h3>Opening and saving images</h3>
<p>To load an image select <i>File->Open</i> and choose the file. After a file is loaded, it becomes visible in the main image panel, and the
@@ -67,9 +69,20 @@ This dialog can be useful to clear marks from images. Marked images show a <b>*<
Marked images can be copied or moved to a selected directory with <i>File->Copy/Move marked files</i>.
After copying or moving, the list of marked files is cleared. The list of marked files will be remembered after quitting the program.</p>
<p>Another way to mark images is in database view where you can select rows and then select mark or unmark action in context menu. Marked
files will be shown with bold text. Third way to mark files is from thumnails view where you can press press <i>Shift</i> and click with left
files will be shown with bold text. Third way to mark files is from thumbnails view where you can press press <i>Shift</i> and click with left
mouse button and drag across thumbnails to mark them. Holding <i>Ctrl</i> will unmark files.</p>
<h3>File system and tree</h3>
<p>File system panel contain list of images in current opened directory. You can select file from this list and it will be displayed. It is also possible to
use arrow keys to go back (left and up) and forth (right and down) between images.</p>
<p>File tree show file system structure. You can right click to show context menu to perform various actions from <i>File</i> menu. There are also few others
<ul>
<li><i>Set as root directory</i> show only this directory and subdirectories</li>
<li><i>Reset root directory</i> show whole file system</li>
<li><i>Go up</i> show directory that is one level above current root directory</li>
</ul>
</p>
<h3>Database of FITS/XISF files</h3>
<p>Tenmon can scan a directory of FITS/XISF files and index metadata from FITS headers into it's internal database. This allows searching and sorting images based on that metadata.</p>
<p>To populate the database, select a directory of FITS/XISF files with <i>File->Index directory</i>.
@@ -82,6 +95,14 @@ Below the table is button to select which columns/properties are displayed.</p>
<p>Also at the bottom of the database panel are three combo boxes and text inputs used for filtering.
Select the property to filter on with the combo box and in the adjacent text box enter a string to search for in that property.
These three combo box contain list of all properties that are found during indexing except first five. First one set searching in file name.
Next two "RA pos" and "DEC pos" allow to filter out indexed images that contain point with entered RA/DEC coordinate. Expected format is three
number separated by space. In case of "DEC pos" it also accept +- sign. Omitting one or two last number is also valid. Some examples "02 12 32" "-12 43 12" "+45 32" "13".
So for RA it means hour, minutes and seconds while for DEC it is degrees, minutes and seconds.
Setting both "RA pos" and "DEC pos" can return images that doesn't contain entered point as it search against minimum and maximum RA/DEC coordinates that images contain.
"RA range" and "DEC range" filter out images which center coordinate is within entered range.
Pressing Enter or clicking on <i>Filter</i> button will filter out database record according to search parameter.
<p>Wildcards:
<ul>
<li><b>%</b> (percent) is a wildcard representing zero or more of any characters.</li>
+23 -1
View File
@@ -23,6 +23,8 @@ img { margin: 5px; }
Le panneau <i>Système de fichier</i> affiche d'autres images dans le même répertoire que l'image chargée.
En haut se trouve le menu principal et en dessous se trouve le panneau <i>Réglage de la luminosité</i> contenant diverses options pour l'étirement automatique des images linéaires.</p>
<p>Tous les panneaux de l'interface peuvent être déplacés et/ou fermés. Tout panneau fermé ou non visible peut être rouvert via le menu <i>Fenêtres encrables</i> en haut.</p>
<p>En bas, il y a une barre d'état qui affiche l'intensité ou la valeur du pixel rouge, vert, bleu sous le curseur de la souris, puis les coordonnées X et Y et,
si l'image contient des métadonnées du World Coordinate System, elle affiche les coordonnées célestes.</p>
<h3>Ouvrir et enregistrer des images</h3>
<p>Pour charger une image, sélectionnez <i>Fichier->Ouvrir</i> et choisissez le fichier. Une fois qu'un fichier est chargé,
@@ -65,17 +67,37 @@ Après le curseur se trouvent 5 boutons pour la luminosité automatique :
<p>Les images peuvent être marquées dans le menu <i>Sélectionner</i>. Pour afficher une liste des seules images marquées, utilisez <i>Sélectionner->Afficher marqué</i>. Cette boîte de dialogue peut être utile pour effacer les marques des images. Les images marquées affichent un caractère <b>*</b> dans la barre de titre de la fenêtre principale. Les images marquées peuvent être copiées ou déplacées vers un répertoire sélectionné avec <i>Fichier->Copier/Déplacer les fichiers marqués</i>. Après la copie ou le déplacement, la liste des fichiers marqués est effacée. La liste des fichiers marqués sera mémorisée après avoir quitté le programme.</p>
<p>Une autre façon de marquer des images est dans la vue de la base de données où vous pouvez sélectionner des lignes, puis sélectionner l'action marquer ou décocher dans le menu contextuel. Les fichiers marqués seront affichés en texte gras. La troisième façon de marquer les fichiers est de voir les vignettes où vous pouvez appuyer sur <i>Maj</i> et cliquer avec le bouton gauche de la souris et faire glisser sur les vignettes pour les marquer. Maintenir <i>Ctrl</i> décochera les fichiers.</p>
<h3>Système de fichier et arborescence</h3>
<p>Le panneau du système de fichiers contient la liste des images du répertoire ouvert. Vous pouvez sélectionner un fichier dans cette liste et il sera affiché. Il est également possible
d'utiliser les touches fléchées pour revenir en arrière (gauche et haut) et avancer (droite et bas) entre les images.</p>
<p>L'arborescence des fichiers montre la structure du système de fichiers. Vous pouvez cliquer avec le bouton droit de la souris pour afficher un menu contextuel permettant d'effectuer diverses actions du menu <i>Fichier</i>. Il y a aussi les actions suivantes
<ul>
<li><i>Définir comme répertoire racine</i> afficher uniquement ce répertoire et ses sous-répertoires</li>
<li><i>Réinitialiser la racine</i> afficher tout le système de fichiers</li>
<li><i>Monter</i> afficher le répertoire qui est un niveau au-dessus du répertoire racine actuel</li>
</ul>
</p>
<h3>Base de donnée de fichiers FITS/XISF</h3>
<p>Tenmon peut analyser un répertoire de fichiers FITS/XISF et indexer les métadonnées des en-têtes FITS dans sa base de données interne. Cela permet de rechercher et de trier des images en fonction de ces métadonnées.</p>
<p>Pour remplir la base de données, sélectionnez un répertoire de fichiers FITS/XISF avec <i>Fichier->Indexer le répertoire</i>. Une fois le répertoire parcouru, les métadonnées analysées à partir des images seront stockées dans la base de données. Pour actualiser la base de données, exécutez <i>Fichier-> Ré-indexer les fichiers</i>. Cela mettra à jour toutes les métadonnées modifiées et supprimera tout enregistrement de fichiers supprimés. Pour indexer de nouveaux fichiers, exécutez simplement à nouveau <i>Fichier->Indexer le répertoire</i>.</p>
<p>La base de données est visualisée via un panneau qui n'est pas visible dans la mise en page par défaut. Pour ajouter le panneau de base de données à la vue, basculez <i>Fenêtres encrables->Base de données FITS/XISF</i>. Une fois visible, le panneau de la base de données affiche la base de données sous forme de tableau avec une colonne pour chaque propriété. Sous le tableau se trouve un bouton pour sélectionner les colonnes/propriétés à afficher.</p>
<p>Également au bas du panneau de la base de données se trouvent trois zones de liste déroulante et des entrées de texte utilisées pour le filtrage. Sélectionnez la propriété à filtrer dans une liste déroulante et dans la zone de texte adjacente, entrez un texte à rechercher pour cette propriété.</p>
<p>Au bas du panneau de la base de données se trouvent également trois boîtes de liste déroulante et des entrées de texte utilisées pour le filtrage. Sélectionnez la propriété à filtrer dans une liste déroulante et dans la zone de texte adjacente, entrez un texte à rechercher pour cette propriété.
Ces trois boîtes contiennent la liste de toutes les propriétés qui sont trouvées pendant l'indexation, sauf les trois premières. La première définit la recherche dans le nom du fichier.
Les deux suivantes "RA pos" et "DEC pos" permettent de filtrer les images indexées qui contiennent un point avec les coordonnées RA/DEC entrée. Le format attendu est trois nombres séparés par un espace.
Dans le cas de "DEC pos", il accepte également le signe +-. L'omission d'un ou deux derniers chiffres est également valable. Quelques exemples "02 12 32" "-12 43 12" "+45 32" "13".
Le fait de définir à la fois "RA pos" et "DEC pos" peut renvoyer des images qui ne contiennent pas le point saisi, car la recherche est faite sur les coordonnées RA/DEC minimum et maximum que les images contiennent.
"RA range" et "DEC range" filtrent les images dont les coordonnées centrale se trouvent dans la plage saisie.
En appuyant sur la touche Enter ou en cliquant sur le bouton <i>Filtre</i>, les enregistrements de la base de données seront filtrés en fonction des paramètres de recherche.
<p>Caractères génériques :
<ul>
<li><b>%</b> (pourcentage) est un caractère générique représentant zéro ou plusieurs caractères.</li>
<li><b>_</b> (trait de soulignement) est un caractère générique pour exactement un caractère quelconque.</li>
<li>En l'absence de caractères génériques, le texte exacte doit correspondre.</li>
</ul>
</p>
<br><img src=":/about/filter.png"><br>
Cet exemple filtre les fichiers où : "Bias" figure dans le nom de fichier, la propriété OBJECT est "M_42" (où le trait de soulignement peut être n'importe quel caractère) et la propriété DATE commence par "2022".
</p>
+70 -15
View File
@@ -22,16 +22,32 @@ bool Database::init()
if(m_database.isValid())
{
m_database.setDatabaseName(dir.absoluteFilePath("database.db"));
m_database.setDatabaseName(dir.absoluteFilePath("database2.db"));
if(m_database.open())
{
m_database.exec("PRAGMA foreign_keys = ON");
m_database.exec("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE, mtime DATETIME)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_headers (id INTEGER PRIMARY KEY AUTOINCREMENT, id_file INTEGER,"
"key VARCHAR(81), value VARCHAR(81), comment VARCHAR(81), FOREIGN KEY(id_file) REFERENCES fits_files(id) ON DELETE CASCADE)");
m_database.exec("CREATE INDEX IF NOT EXISTS key_value ON fits_headers(key, value)");
m_database.exec("CREATE INDEX IF NOT EXISTS id_file ON fits_headers(id_file)");
int version = checkVersion();
if(version == 0)
{
m_database.exec("PRAGMA user_version = 1");
m_database.exec("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE, mtime DATETIME,"
" minRa REAL, maxRa REAL, minDec REAL, maxDec REAL, crVal1 REAL, crVal2 REAL)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_headers (id INTEGER PRIMARY KEY AUTOINCREMENT, id_file INTEGER,"
"key VARCHAR(81), value VARCHAR(81), comment VARCHAR(81), FOREIGN KEY(id_file) REFERENCES fits_files(id) ON DELETE CASCADE)");
m_database.exec("CREATE INDEX IF NOT EXISTS key_value ON fits_headers(key, value)");
m_database.exec("CREATE INDEX IF NOT EXISTS id_file ON fits_headers(id_file)");
m_database.exec("CREATE INDEX IF NOT EXISTS minRa_idx ON fits_files(minRa)");
m_database.exec("CREATE INDEX IF NOT EXISTS maxRa_idx ON fits_files(maxRa)");
m_database.exec("CREATE INDEX IF NOT EXISTS minDec_idx ON fits_files(minDec)");
m_database.exec("CREATE INDEX IF NOT EXISTS maxDec_idx ON fits_files(maxDec)");
}
else if(version > 1)
{
qDebug() << "Database version is too new";
return false;
}
QSqlError error = m_database.lastError();
if(error.type() == QSqlError::NoError)
@@ -45,6 +61,8 @@ bool Database::init()
m_insertFile = QSqlQuery(m_database);
m_insertFile.prepare("INSERT INTO fits_files (file, mtime) VALUES (?, ?)");
m_insertFileWcs = QSqlQuery(m_database);
m_insertFileWcs.prepare("INSERT INTO fits_files (file, mtime, minRa, maxRa, minDec, maxDec, crVal1, crVal2) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
m_insertFitsHeader = QSqlQuery(m_database);
m_insertFitsHeader.prepare("INSERT INTO fits_headers (id_file, key, value, comment) VALUES (?, ?, ?, ?)");
m_checkFile = QSqlQuery(m_database);
@@ -55,7 +73,6 @@ bool Database::init()
m_deleteFile.prepare("DELETE FROM fits_files WHERE id=?");
return true;
}
qDebug() << error.text();
}
}
@@ -129,6 +146,15 @@ bool Database::checkError(QSqlQuery &query)
}
}
int Database::checkVersion()
{
QSqlDatabase db = QSqlDatabase::database();
QSqlQuery query = db.exec("PRAGMA user_version");
if(query.next())
return query.value(0).toInt();
return -1;
}
static QStringList nameFilters = {"*.fit", "*.fits", "*.xisf"};
static int countFiles(const QDir &dir, int count = 0)
@@ -148,9 +174,14 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
if(indexDir2(dir, progress))
{
database.commit();
emit databaseChanged();
}
else
{
database.rollback();
}
}
void Database::reindex(QProgressDialog *progress)
@@ -242,16 +273,40 @@ bool Database::indexFile(const QFileInfo &file)
qlonglong last_id = -1;
if(ok)
{
m_insertFile.bindValue(0, filePath);
m_insertFile.bindValue(1, mtime);
if(!m_insertFile.exec())
if(info.wcs)
{
qDebug() << m_insertFile.lastError();
return false;
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
info.wcs->calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
qDebug() << "bounds" << minRa << maxRa << minDec << maxDec;
m_insertFileWcs.bindValue(0, filePath);
m_insertFileWcs.bindValue(1, mtime);
m_insertFileWcs.bindValue(2, minRa);
m_insertFileWcs.bindValue(3, maxRa);
m_insertFileWcs.bindValue(4, minDec);
m_insertFileWcs.bindValue(5, maxDec);
m_insertFileWcs.bindValue(6, crVal1);
m_insertFileWcs.bindValue(7, crVal2);
if(!m_insertFileWcs.exec())
{
qDebug() << m_insertFileWcs.lastError();
return false;
}
last_id = m_insertFileWcs.lastInsertId().toLongLong();
}
last_id = m_insertFile.lastInsertId().toLongLong();
else
{
m_insertFile.bindValue(0, filePath);
m_insertFile.bindValue(1, mtime);
if(!m_insertFile.exec())
{
qDebug() << m_insertFile.lastError();
return false;
}
last_id = m_insertFile.lastInsertId().toLongLong();
}
QVariantList file_id, keys, values, comments;
for(auto &record : info.fitsHeader)
for(const auto &record : info.fitsHeader)
{
file_id << last_id;
keys << QString(record.key);
+4
View File
@@ -15,6 +15,7 @@ class Database : public QObject
QSqlQuery m_isMarkedQuery;
QSqlQuery m_insertFile;
QSqlQuery m_insertFileWcs;
QSqlQuery m_insertFitsHeader;
QSqlQuery m_checkFile;
QSqlQuery m_headerKeywords;
@@ -39,6 +40,9 @@ protected:
bool indexDir2(const QDir &dir, QProgressDialog *progress);
bool indexFile(const QFileInfo &file);
bool checkError(QSqlQuery &query);
int checkVersion();
signals:
void databaseChanged();
};
#endif // DATABASE_H
+74 -7
View File
@@ -8,10 +8,34 @@
#include <QDebug>
#include <QMenu>
#include <QContextMenuEvent>
#include <QRegExp>
#include <iostream>
const QStringList DEFAULT_COLUMNS = {"EXPTIME", "OBJECT", "RA", "DEC"};
double RA(const QString &ra)
{
QRegularExpression reg("(\\d+)\\s*(\\d+)?\\s*(\\d+)?");
QRegularExpressionMatch match = reg.match(ra);
double h = match.captured(1).toDouble();
double m = match.captured(2).toDouble();
double s = match.captured(3).toDouble();
qDebug() << match.capturedTexts() << h << m << s;
return h*15 + m*0.25 + s*15/3600;
}
double DEC(const QString &dec)
{
QRegularExpression reg("([\\+\\-])?(\\d+)\\s*(\\d+)?\\s*(\\d+)?");
QRegularExpressionMatch match = reg.match(dec);
double sign = match.captured(1) == "-" ? -1 : 1;
double d = match.captured(2).toDouble();
double m = match.captured(3).toDouble();
double s = match.captured(4).toDouble();
qDebug() << match.capturedTexts() << sign << d << m << s;
return sign * (d + m/60 + s/3600);
}
SelectColumnsDialog::SelectColumnsDialog(QWidget *parent) : QDialog(parent)
{
m_listWidget = new QListWidget(this);
@@ -73,17 +97,19 @@ void FITSFileModel::setColumns(const QStringList &columns)
prepareQuery();
}
void FITSFileModel::setFilter(const QStringList &key, const QStringList &value)
void FITSFileModel::setFilter(const QStringList &key, const QStringList &value, const QStringList &limit)
{
if(value.isEmpty())
{
m_key.clear();
m_value.clear();
m_limit.clear();
}
else
{
m_key = key;
m_value = value;
m_limit = limit;
}
prepareQuery();
}
@@ -130,12 +156,20 @@ void FITSFileModel::prepareQuery()
{
QString cols;
QString join;
QString where;
QStringList where;
QString sql = "SELECT f.file,";
for(int i=0; i<m_value.size(); i++)
{
if(m_key[i] == "file")
where = QString(" WHERE f.file LIKE '%1'").arg(m_value[i]);
where.append(QString(" f.file LIKE '%1' ").arg(m_value[i]));
else if(m_key[i] == "RA pos")
where.append(QString(" %1 BETWEEN f.minRa AND f.maxRa ").arg(RA(m_value[i])));
else if(m_key[i] == "DEC pos")
where.append(QString(" %1 BETWEEN f.minDec AND f.maxDec ").arg(DEC(m_value[i])));
else if(m_key[i] == "RA range")
where.append(QString(" crVal1 BETWEEN %1 AND %2 ").arg(RA(m_value[i])).arg(RA(m_limit[i])));
else if(m_key[i] == "DEC range")
where.append(QString(" crVal2 BETWEEN %1 AND %2 ").arg(DEC(m_value[i])).arg(DEC(m_limit[i])));
else
join += QString(" JOIN fits_headers AS s%1 ON f.id=s%1.id_file AND s%1.key='%2' AND s%1.value LIKE '%3'").arg(i).arg(m_key[i]).arg(m_value[i]);
}
@@ -150,7 +184,7 @@ void FITSFileModel::prepareQuery()
sql += cols;
sql += " FROM fits_files AS f";
sql += join;
sql += where;
if(!where.isEmpty())sql += " WHERE " + where.join("AND");
sql += " GROUP BY f.id" + m_sort;
setQuery(sql);
setHeaderData(0, Qt::Horizontal, tr("File name"));
@@ -232,21 +266,52 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
m_model->filesUnmarked(indexes);
});
auto addFilterItems = [](QComboBox *combobox, const QStringList &fitsKeywords)
{
combobox->clear();
combobox->addItem("file");
combobox->addItem("RA pos");
combobox->addItem("DEC pos");
combobox->addItem("RA range");
combobox->addItem("DEC range");
combobox->addItems(fitsKeywords);
};
QStringList fitsKeywords = m_database->getFitsKeywords();
for(int i=0; i<3; i++)
{
m_filterKeyword[i] = new QComboBox(this);
m_filterKeyword[i]->addItem("file");
m_filterKeyword[i]->addItems(m_database->getFitsKeywords());
addFilterItems(m_filterKeyword[i], fitsKeywords);
m_search[i] = new QLineEdit(this);
m_search[i]->setPlaceholderText(tr("Text to search, you can % as wildcard"));
m_limit[i] = new QLineEdit(this);
m_limit[i]->hide();
connect(m_filterKeyword[i], static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this, i](int index){
if(index == 3 || index == 4)m_limit[i]->show();
else m_limit[i]->hide();
});
connect(m_search[i], &QLineEdit::returnPressed, this, &DataBaseView::applyFilter);
connect(m_limit[i], &QLineEdit::returnPressed, this, &DataBaseView::applyFilter);
hlayout->addWidget(m_filterKeyword[i]);
hlayout->addWidget(m_search[i]);
hlayout->addWidget(m_limit[i]);
}
QPushButton *filterButton = new QPushButton(tr("Filter"), this);
connect(filterButton, SIGNAL(pressed()), this, SLOT(applyFilter()));
hlayout->addWidget(filterButton);
connect(m_database, &Database::databaseChanged, [this, &addFilterItems](){
QStringList fitsKeywords = m_database->getFitsKeywords();
for(int i=0; i<3; i++)
addFilterItems(m_filterKeyword[i], fitsKeywords);
applyFilter();
});
}
DataBaseView::~DataBaseView()
@@ -284,6 +349,7 @@ void DataBaseView::applyFilter()
{
QStringList keys;
QStringList values;
QStringList limits;
for(int i=0; i<3; i++)
{
QString key = m_filterKeyword[i]->currentText();
@@ -291,7 +357,8 @@ void DataBaseView::applyFilter()
{
keys.append(key);
values.append(m_search[i]->text());
limits.append(m_limit[i]->text());
}
}
m_model->setFilter(keys, values);
m_model->setFilter(keys, values, limits);
}
+3 -1
View File
@@ -27,13 +27,14 @@ class FITSFileModel : public QSqlQueryModel
QString m_sort;
QStringList m_key;
QStringList m_value;
QStringList m_limit;
QSet<QString> m_markedFiles;
Database *m_database;
public:
explicit FITSFileModel(Database *database, QObject *parent = nullptr);
void sort(int column, Qt::SortOrder order) override;
void setColumns(const QStringList &columns);
void setFilter(const QStringList &key, const QStringList &value);
void setFilter(const QStringList &key, const QStringList &value, const QStringList &limit);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void filesMarked(const QModelIndexList &indexes);
void filesUnmarked(const QModelIndexList &indexes);
@@ -61,6 +62,7 @@ class DataBaseView : public QWidget
FITSFileModel *m_model;
QComboBox *m_filterKeyword[3];
QLineEdit *m_search[3];
QLineEdit *m_limit[3];
public:
explicit DataBaseView(Database *database, QWidget *parent = nullptr);
~DataBaseView() override;
+4 -4
View File
@@ -17,7 +17,7 @@ FilesystemWidget::FilesystemWidget(QAbstractItemModel *model, QWidget *parent) :
setLayout(layout);
connect(m_listView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(fileClicked(QModelIndex)));
connect(m_listView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FilesystemWidget::fileClicked);
}
void FilesystemWidget::setDir(const QString &dir)
@@ -29,14 +29,14 @@ void FilesystemWidget::setDir(const QString &dir)
void FilesystemWidget::selectFile(int row)
{
QModelIndex index = m_model->index(row, 0);
m_listView->selectionModel()->clearSelection();
m_listView->selectionModel()->select(index, QItemSelectionModel::SelectCurrent);
m_listView->scrollTo(index);
}
void FilesystemWidget::fileClicked(const QModelIndex &index)
void FilesystemWidget::fileClicked(const QModelIndex &index, const QModelIndex &)
{
emit fileSelected(index.row());
if(index.isValid())
emit fileSelected(index.row());
}
Filetree::Filetree(QWidget *parent) : QTreeView(parent)
+1 -1
View File
@@ -16,7 +16,7 @@ public:
void setDir(const QString &dir);
private slots:
void selectFile(int row);
void fileClicked(const QModelIndex &index);
void fileClicked(const QModelIndex &index, const QModelIndex &);
signals:
void fileSelected(int row);
};
+229
View File
@@ -1,6 +1,10 @@
#include "imageinfo.h"
#include <QSettings>
#include <QTime>
#include <QHeaderView>
#include <wcslib/wcshdr.h>
#include <wcslib/wcsfix.h>
#include "pcl/FITSHeaderKeyword.h"
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
@@ -9,6 +13,44 @@ bool FITSRecord::editable() const
return noEditableKey.count(key);
}
FITSRecord::FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment) :
key(key), value(value), comment(comment)
{
}
FITSRecord::FITSRecord(const pcl::FITSHeaderKeyword &record)
{
key = record.name.c_str();
comment = record.comment.c_str();
QString string = record.value.c_str();
if(string.startsWith('\'') && string.endsWith('\''))
{
string.chop(1);
string.remove(0, 1);
}
bool isint;
bool isdouble;
double vald = string.toDouble(&isdouble);
long long vall = string.toLongLong(&isint);
if(isint)
value = vall;
else if(isdouble)
value = vald;
else if(string == "T" || string == "F")
value = string == "T";
else
value = string;
}
QByteArray FITSRecord::valueToByteArray() const
{
if(value.type() == QVariant::Bool)
return value.toBool() ? "T" : "F";
else
return value.toString().toLatin1();
}
ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
{
setColumnCount(3);
@@ -47,3 +89,190 @@ void ImageInfo::setInfo(const ImageInfoData &info)
}
expandAll();
}
void WCSData::freeWCS()
{
wcsvfree(&nwcs, &wcs);
nwcs = 0;
wcs = nullptr;
}
WCSData::WCSData(int width, int height, char *header, int nrec) :
width(width),
height(height)
{
int nreject = 0;
int status = wcspih(header, nrec, 1, 0, &nreject, &nwcs, &wcs);
if(status != 0)
{
freeWCS();
return;
}
status = cdfix(wcs);
if(status > 0 || wcs->crpix[0] == 0)
freeWCS();
}
WCSData::WCSData(int width, int height, const QVector<FITSRecord> &header) :
width(width),
height(height)
{
int status = 0;
QByteArray str;
int nrec = 1;
for(const FITSRecord &record : header)
{
if(record.key.startsWith("PV"))continue;
QByteArray rec;
rec.append(record.key.leftJustified(8, ' '));
rec.append("= ");
rec.append(record.value.toString().toLatin1());
rec.append(" / ");
rec.append(record.comment);
str.append(rec.leftJustified(80, ' ', true));
nrec++;
}
str.append(QByteArray("END").leftJustified(80));
int nreject = 0;
status = wcspih(str.data(), nrec, 1, 0, &nreject, &nwcs, &wcs);
if(status != 0)
{
freeWCS();
return;
}
status = cdfix(wcs);
if(status > 0 || wcs->crpix[0] == 0)
freeWCS();
}
WCSData::~WCSData()
{
if(wcs)
freeWCS();
}
bool WCSData::pixelToWorld(const QPointF &pixel, SkyPoint &point) const
{
if(!valid())return false;
double pixcrd[2] = {pixel.x(), pixel.y()};
double imgcrd[8] = {0};
double phi = 0;
double theta = 0;
double world[8] = {0};
int stat[NWCSFIX] = {0};
int status = wcsp2s(wcs, 1, 2, pixcrd, imgcrd, &phi, &theta, world, stat);
if(status == 0)
{
point = SkyPoint(world[0], world[1]);
return true;
}
return false;
}
bool WCSData::worldToPixel(const SkyPoint &point, QPointF &pixel) const
{
if(!valid())return false;
double world[2] = {point.RA(), point.DEC()};
double phi = 0;
double theta = 0;
double imgcrd[8] = {0};
double pixcrd[8] = {0};
int stat[NWCSFIX] = {0};
int status = wcss2p(wcs, 1, 2, world, &phi, &theta, imgcrd, pixcrd, stat);
if(status == 0)
{
pixel = QPointF(pixcrd[0], pixcrd[1]);
return true;
}
return false;
}
void WCSData::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
{
if(wcs == nullptr)return;
minRa = 1000;
maxRa = -1000;
minDec = 1000;
maxDec = -1000;
if(wcs->crval)
{
crVal1 = wcs->crval[0];
crVal2 = wcs->crval[1];
}
else
{
crVal1 = crVal2 = NAN;
}
auto update = [&](const QPointF &pixel)
{
SkyPoint point;
pixelToWorld(pixel, point);
minRa = std::min(minRa, point.RA());
maxRa = std::max(maxRa, point.RA());
minDec = std::min(minDec, point.DEC());
maxDec = std::max(maxDec, point.DEC());
};
for(int x=0; x<width; x++)
{
update(QPointF(x, 0));
update(QPointF(x, height - 1));
}
for(int y=0; y<height; y++)
{
update(QPointF(0, y));
update(QPointF(width - 1, y));
}
QPointF ncp;
QPointF scp;
QRectF s(0, 0, width - 1, height - 1);
if(worldToPixel(SkyPoint(0, 90), ncp))
{
if(s.contains(ncp))
maxDec = 90;
}
if(worldToPixel(SkyPoint(0, -90), scp))
{
if(s.contains(scp))
minDec = -90;
}
}
SkyPoint::SkyPoint() : ra(NAN), dec(NAN)
{
}
SkyPoint::SkyPoint(double ra, double dec) : ra(ra), dec(dec)
{
}
void SkyPoint::set(double ra, double dec)
{
this->ra = ra;
this->dec = dec;
}
QString SkyPoint::toString() const
{
if(std::isnan(ra) || std::isnan(dec))
return QString();
QTime t(0, 0);
t = t.addSecs(ra * 240);
double deg, min, sec;
min = std::modf(dec, &deg) * 60;
sec = std::modf(min, &min) * 60;
return QString("RA: %1 DEC: %2° %3' %4\"").arg(t.toString("HH'h' mm'm' ss's'")).arg(deg, 2, 'f', 0, '0').arg(min, 2, 'f', 0, '0').arg(sec, 2, 'f', 0, '0');
}
+41
View File
@@ -2,6 +2,11 @@
#define IMAGEINFO_H
#include <QTreeWidget>
#include <wcslib/wcs.h>
#include <cmath>
#include <memory>
namespace pcl { class FITSHeaderKeyword; }
struct FITSRecord
{
@@ -9,12 +14,48 @@ struct FITSRecord
QVariant value;
QByteArray comment;
bool editable() const;
FITSRecord(){}
FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment);
FITSRecord(const pcl::FITSHeaderKeyword &record);
QByteArray valueToByteArray() const;
};
class SkyPoint
{
double ra = NAN;
double dec = NAN;
public:
SkyPoint();
SkyPoint(double ra, double dec);
void set(double ra, double dec);
double RA() const { return ra; }
double DEC() const { return dec; }
QString toString() const;
};
class WCSData
{
int nwcs = 0;
struct wcsprm *wcs = nullptr;
int width;
int height;
void freeWCS();
public:
WCSData(int width, int height, char *header, int nrec);
WCSData(int width, int height, const QVector<FITSRecord> &header);
WCSData(const WCSData &) = delete;
~WCSData();
bool pixelToWorld(const QPointF &pixel, SkyPoint &point) const;
bool worldToPixel(const SkyPoint &point, QPointF &pixel) const;
void calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
bool valid() const { return wcs; };
};
struct ImageInfoData
{
QVector<FITSRecord> fitsHeader;
QVector<QPair<QString, QString>> info;
std::shared_ptr<WCSData> wcs;
};
Q_DECLARE_METATYPE(ImageInfoData);
+52 -25
View File
@@ -3,6 +3,7 @@
#include <QDir>
#include "loadrunable.h"
#include "rawimage.h"
#include "database.h"
using namespace std;
@@ -50,9 +51,9 @@ QString Image::name() const
return m_name;
}
RawImage *Image::rawImage()
std::shared_ptr<RawImage> Image::rawImage()
{
return m_rawImage.get();
return m_rawImage;
}
const RawImage *Image::thumbnail() const
@@ -97,9 +98,10 @@ void Image::thumbnailLoadFinish(void *rawImage)
emit thumbnailLoaded(this);
}
ImageRingList::ImageRingList(QObject *parent) : QAbstractItemModel(parent)
ImageRingList::ImageRingList(Database *database, QObject *parent) : QAbstractItemModel(parent)
, m_liveMode(false)
, m_analyzeLevel(None)
, m_database(database)
{
connect(&m_fileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(dirChanged(QString)));
m_thumbPool = new QThreadPool(this);
@@ -210,30 +212,38 @@ void ImageRingList::loadFile(int row)
{
if(row < m_images.size())
{
m_firstImage = m_currImage = m_lastImage = m_images.begin()+row;
if(m_images.empty())
return;
(*m_currImage)->load();
m_width = DEFAULT_WIDTH<m_images.size()/2 ? DEFAULT_WIDTH : m_images.size()/2;
if(m_liveMode)
m_width = 0;
for(int i=0; i<m_width; i++)
int diff = m_currImage != m_images.end() ? row - (m_currImage - m_images.begin()) : -100;
if(diff == 1)
increment();
else if(diff == -1)
decrement();
else
{
m_firstImage = decrement(m_firstImage);
(*m_firstImage)->load();
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load();
}
if(m_lastImage != m_firstImage)
{
QList<ImagePtr>::iterator iter = increment(m_lastImage);
while(m_firstImage != iter)
m_firstImage = m_currImage = m_lastImage = m_images.begin()+row;
if(m_images.empty())
return;
(*m_currImage)->load();
m_width = DEFAULT_WIDTH<m_images.size()/2 ? DEFAULT_WIDTH : m_images.size()/2;
if(m_liveMode)
m_width = 0;
for(int i=0; i<m_width; i++)
{
(*iter)->release();
iter = increment(iter);
m_firstImage = decrement(m_firstImage);
(*m_firstImage)->load();
m_lastImage = increment(m_lastImage);
(*m_lastImage)->load();
}
if(m_lastImage != m_firstImage)
{
QList<ImagePtr>::iterator iter = increment(m_lastImage);
while(m_firstImage != iter)
{
(*iter)->release();
iter = increment(iter);
}
}
}
}
@@ -265,6 +275,15 @@ QStringList ImageRingList::imageNames() const
return ret;
}
void ImageRingList::updateMark()
{
if(m_images.size())
{
QModelIndex idx = index(m_currImage - m_images.begin(), 0);
emit dataChanged(idx, idx, {Qt::FontRole});
}
}
QModelIndex ImageRingList::index(int row, int column, const QModelIndex &parent) const
{
return createIndex(row, column, m_images.at(row).get());
@@ -297,6 +316,13 @@ QVariant ImageRingList::data(const QModelIndex &index, int role) const
QFileInfo info(m_images.at(index.row())->name());
return info.fileName();
}
case Qt::FontRole:
{
bool marked = m_database->isMarked(m_images.at(index.row())->name());
QFont font;
font.setBold(marked);
return font;
}
default:
return QVariant();
}
@@ -333,6 +359,7 @@ void ImageRingList::setFiles(const QStringList files, const QString &currentFile
index = 0;
endResetModel();
m_currImage = m_images.end();
loadFile(index);
}
+7 -3
View File
@@ -19,7 +19,7 @@ class Image : public QObject
bool m_released;
bool m_current;
int m_number;
std::unique_ptr<RawImage> m_rawImage;
std::shared_ptr<RawImage> m_rawImage;
std::unique_ptr<RawImage> m_thumbnail;
QString m_name;
ImageInfoData m_info;
@@ -30,7 +30,7 @@ public:
void loadThumbnail(QThreadPool *pool);
void release();
QString name() const;
RawImage* rawImage();
std::shared_ptr<RawImage> rawImage();
const RawImage* thumbnail() const;
ImageInfoData info() const;
bool isCurrent() const;
@@ -45,6 +45,8 @@ protected slots:
typedef std::shared_ptr<Image> ImagePtr;
class Database;
class ImageRingList : public QAbstractItemModel
{
Q_OBJECT
@@ -57,8 +59,9 @@ class ImageRingList : public QAbstractItemModel
bool m_liveMode;
AnalyzeLevel m_analyzeLevel;
QThreadPool *m_thumbPool;
Database *m_database;
public:
explicit ImageRingList(QObject *parent = 0);
explicit ImageRingList(Database *database, QObject *parent = 0);
~ImageRingList() override;
bool setDir(const QString path, const QString &currentFile = QString());
void setFile(const QString &file);
@@ -75,6 +78,7 @@ public:
void stopLoading();
int imageCount() const;
QStringList imageNames() const;
void updateMark();
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
+73 -22
View File
@@ -25,8 +25,9 @@ const RawImageType rawImageTypes[] = {
{QOpenGLTexture::Red, QOpenGLTexture::R16_UNorm, QOpenGLTexture::UInt16, true},
{QOpenGLTexture::Red, QOpenGLTexture::R32F, QOpenGLTexture::Float32, true},
{QOpenGLTexture::RGB, QOpenGLTexture::RGB8_UNorm, QOpenGLTexture::UInt8, false},
{QOpenGLTexture::BGRA,QOpenGLTexture::RGB8_UNorm, QOpenGLTexture::UInt8, false},
{QOpenGLTexture::RGBA,QOpenGLTexture::RGB8_UNorm, QOpenGLTexture::UInt8, false},
{QOpenGLTexture::RGB, QOpenGLTexture::RGB16_UNorm, QOpenGLTexture::UInt16, false},
{QOpenGLTexture::RGBA, QOpenGLTexture::RGB16_UNorm, QOpenGLTexture::UInt16, false},
{QOpenGLTexture::RGB, QOpenGLTexture::RGB32F, QOpenGLTexture::Float32, false}
};
@@ -66,6 +67,7 @@ ImageWidget::ImageWidget(Database *database, QWidget *parent) : QOpenGLWidget(pa
m_updateTimer = new QTimer(this);
m_updateTimer->setInterval(500);
m_updateTimer->setSingleShot(true);
m_sizesDirty = false;
connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(update()));
setAcceptDrops(true);
QTimer::singleShot(1000, [this](){
@@ -75,6 +77,8 @@ ImageWidget::ImageWidget(Database *database, QWidget *parent) : QOpenGLWidget(pa
QCoreApplication::exit(-1);
}
});
setMouseTracking(true);
}
ImageWidget::~ImageWidget()
@@ -82,10 +86,12 @@ ImageWidget::~ImageWidget()
makeCurrent();
}
void ImageWidget::setImage(const RawImage *image, int index)
void ImageWidget::setImage(std::shared_ptr<RawImage> image, int index)
{
if(image == nullptr)return;
m_rawImage = image;
m_imgWidth = image->width();
m_imgHeight = image->height();
m_currentImg = index;
@@ -100,13 +106,18 @@ void ImageWidget::setImage(const RawImage *image, int index)
m_image->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);
m_image->setWrapMode(QOpenGLTexture::ClampToEdge);
m_image->setBorderColor(0, 0, 0, 0);
m_image->setData(0, rawImageType.pixelFormat, rawImageType.dataType, image->data(), m_transferOptions.get());
m_image->setData(0, rawImageType.pixelFormat, rawImageType.dataType, (const void*)image->data(), m_transferOptions.get());
m_image->setLevelOfDetailRange(m_superpixel ? 1 : 0, m_image->mipMaxLevel());
m_image->generateMipMaps();
m_bwImg = rawImageType.bw;
update();
}
void ImageWidget::setWCS(std::shared_ptr<WCSData> wcs)
{
m_wcs = wcs;
}
void ImageWidget::setScale(float scale)
{
m_scale = scale;
@@ -137,11 +148,6 @@ void ImageWidget::allocateThumbnails(const QStringList &paths)
m_thumbnailTexture->setSize(THUMB_SIZE, THUMB_SIZE);
m_thumbnailTexture->setLayers(paths.size());
m_thumbnailTexture->allocateStorage();
m_bufferSizes->bind();
float *tmp = new float[count*3];
memset(tmp, 0, count * sizeof(float)*3);
m_bufferSizes->allocate(tmp, count * sizeof(float)*3);
delete [] tmp;
}
void ImageWidget::setMTFParams(float low, float mid, float high)
@@ -199,8 +205,7 @@ void ImageWidget::thumbnailLoaded(const Image *image)
m_thumbnailTexture->setData(0, image->number(), QOpenGLTexture::RGB, QOpenGLTexture::UInt16, raw->data(), m_transferOptions.get());
float a = raw->thumbAspect();
int sizes[3] = { std::max(1, a > 1.0f ? THUMB_SIZE : (int)(THUMB_SIZE * a)), std::max(1, a < 1.0f ? THUMB_SIZE : (int)(THUMB_SIZE / a)), image->number() };
m_bufferSizes->bind();
m_bufferSizes->write(image->number() * sizeof(sizes), sizes, sizeof(sizes));
m_sizesDirty = true;
m_thumnails[image->number()].size = QSize(sizes[0], sizes[1]);
if(!m_updateTimer->isActive())m_updateTimer->start();
}
@@ -227,12 +232,26 @@ void ImageWidget::paintGL()
{
m_vaoThumb->bind();
m_thumbnailTexture->bind(1);
if(m_sizesDirty)
{
m_bufferSizes->bind();
int i = 0;
std::vector<int> sizes(m_thumbnailCount*3);
for(auto &s : m_thumnails)
{
sizes[3*i] = s.size.width();
sizes[3*i+1] = s.size.height();
sizes[3*i+2] = i;
i++;
}
m_bufferSizes->allocate(&sizes[0], sizes.size()*sizeof(int));
m_sizesDirty = false;
}
m_thumbnailProgram->bind();
f->glUniform3i(m_thumbnailProgram->uniformLocation("viewport_row"), width(), height(), width()/THUMB_SIZE_BORDER);
m_thumbnailProgram->setUniformValue("mtf_param", m_low, m_mid, m_high);
m_thumbnailProgram->setUniformValue("invert", m_invert);
m_thumbnailProgram->setUniformValue("offset", 0, m_dy);
f3->glVertexAttribDivisor(m_thumbnailProgram->attributeLocation("imageSize_num"), 1);
QMatrix4x4 mvp;
mvp.ortho(rect());
m_thumbnailProgram->setUniformValue("mvp", mvp);
@@ -368,10 +387,10 @@ void ImageWidget::initializeGL()
m_bufferSizes->setUsagePattern(QOpenGLBuffer::StaticDraw);
m_bufferSizes->create();
m_bufferSizes->bind();
m_bufferSizes->allocate(12);
m_thumbnailProgram->enableAttributeArray("imageSize_num");
m_thumbnailProgram->setAttributeBuffer("imageSize_num", GL_FLOAT, 0, 3);
f3->glVertexAttribIPointer(m_thumbnailProgram->attributeLocation("imageSize_num"), 3, GL_INT, 0, nullptr);
f3->glVertexAttribDivisor(m_thumbnailProgram->attributeLocation("imageSize_num"), 1);
m_image = std::unique_ptr<QOpenGLTexture>(new QOpenGLTexture(QOpenGLTexture::Target2D));
@@ -387,8 +406,7 @@ void ImageWidget::initializeGL()
m_thumbnailTexture->setLayers(1);
m_thumbnailTexture->allocateStorage();
m_thumbnailTexture->bind(1);
m_thumbnailTexture->setMinificationFilter(QOpenGLTexture::Linear);
m_thumbnailTexture->setMinificationFilter(QOpenGLTexture::Linear);
m_thumbnailTexture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
m_transferOptions = std::unique_ptr<QOpenGLPixelTransferOptions>(new QOpenGLPixelTransferOptions);
m_transferOptions->setAlignment(1);
@@ -419,7 +437,7 @@ void ImageWidget::dropEvent(QDropEvent *event)
void ImageWidget::mousePressEvent(QMouseEvent *event)
{
if(m_thumbnailCount && event->button() == Qt::LeftButton && event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier))
if(m_showThumbnails && event->button() == Qt::LeftButton && event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier))
m_selecting = true;
if(m_selecting)
@@ -438,6 +456,35 @@ void ImageWidget::mouseMoveEvent(QMouseEvent *event)
}
else
event->ignore();
if(!m_showThumbnails && m_rawImage)
{
float dx = m_dx;
float dy = m_dy;
if(width() > m_image->width()*m_scale)
dx = -width()*0.5f + m_image->width()*m_scale*0.5f;
if(height() > m_image->height()*m_scale)
dy = -height()*0.5f + m_image->height()*m_scale*0.5f;
QVector2D offset(dx, dy);
QVector2D pos = QVector2D(event->pos());
QVector2D pix = (pos + offset) / m_scale;
QVector3D rgb;
SkyPoint sky;
if(m_wcs)
{
m_wcs->pixelToWorld(QPointF(pix.x(), pix.y()), sky);
}
if(m_rawImage->pixel(pix.x(), pix.y(), rgb))
{
if(m_bwImg)
emit status(tr("L:%1").arg(rgb.x()), tr("X:%3 Y:%4").arg((int)pix.x()).arg((int)pix.y()), sky.toString());
else
emit status(tr("R:%1 G:%2 B:%3").arg(rgb.x()).arg(rgb.y()).arg(rgb.z()), tr("X:%3 Y:%4").arg((int)pix.x()).arg((int)pix.y()), sky.toString());
}
}
}
void ImageWidget::mouseReleaseEvent(QMouseEvent *event)
@@ -525,13 +572,17 @@ ImageScrollAreaGL::~ImageScrollAreaGL()
}
void ImageScrollAreaGL::setImage(RawImage *image, int index)
void ImageScrollAreaGL::setImage(Image *image)
{
m_imageWidget->setImage(image, index);
m_imgWidth = image->width();
m_imgHeight = image->height();
if(m_bestFit)bestFit();
updateScrollbars();
if(image && image->rawImage())
{
m_imageWidget->setImage(image->rawImage(), image->number());
m_imageWidget->setWCS(image->info().wcs);
m_imgWidth = image->rawImage()->width();
m_imgHeight = image->rawImage()->height();
if(m_bestFit)bestFit();
updateScrollbars();
}
}
ImageWidget *ImageScrollAreaGL::imageWidget()
+7 -2
View File
@@ -39,6 +39,8 @@ class ImageWidget : public QOpenGLWidget
std::unique_ptr<QOpenGLVertexArrayObject> m_vaoThumb;
std::unique_ptr<QOpenGLPixelTransferOptions> m_transferOptions;
std::unique_ptr<QOpenGLTexture> m_thumbnailTexture;
std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSData> m_wcs;
int m_width, m_height;
int m_imgWidth, m_imgHeight;
int m_currentImg;
@@ -54,14 +56,16 @@ class ImageWidget : public QOpenGLWidget
bool m_superpixel;
bool m_showThumbnails;
bool m_selecting;
bool m_sizesDirty;
int m_thumbnailCount;
QVector<ImageThumb> m_thumnails;
Database *m_database;
public:
explicit ImageWidget(Database *database, QWidget *parent = nullptr);
~ImageWidget() override;
void setImage(const RawImage *image, int index);
void setImage(std::shared_ptr<RawImage> image, int index);
void setImage(const QPixmap &pixmap);
void setWCS(std::shared_ptr<WCSData> wcs);
void setScale(float scale);
void blockRepaint(bool block);
void allocateThumbnails(const QStringList &paths);
@@ -85,6 +89,7 @@ protected:
void thumbSelect(QMouseEvent *event);
signals:
void fileDropped(const QString &path);
void status(const QString &value, const QString &pixelCoords, const QString &celestialCoords);
};
class ImageScrollAreaGL : public QWidget
@@ -101,7 +106,7 @@ class ImageScrollAreaGL : public QWidget
public:
explicit ImageScrollAreaGL(Database *database, QWidget *parent = nullptr);
~ImageScrollAreaGL() override;
void setImage(RawImage *image, int index);
void setImage(Image *image);
ImageWidget* imageWidget();
void setThumbnails(int count);
protected:
+27 -6
View File
@@ -12,6 +12,7 @@
#include <pcl/XISF.h>
#include "rawimage.h"
#include "starfit.h"
#include "wcslib/wcshdr.h"
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, bool thumbnail) :
m_file(file),
@@ -131,6 +132,9 @@ bool loadRAW(const QString path, ImageInfoData &info, RawImage **image)
int loadFITSHeader(fitsfile *file, ImageInfoData &info)
{
int imgtype;
int naxis;
long naxes[3] = {0};
int nexist;
int status = 0;
char key[FLEN_KEYWORD];
@@ -138,6 +142,7 @@ int loadFITSHeader(fitsfile *file, ImageInfoData &info)
char comm[FLEN_COMMENT];
char strval[FLEN_VALUE];
QVariant var;
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
fits_get_hdrspace(file, &nexist, nullptr, &status);
for(int i=1; i<=nexist; i++)
{
@@ -156,16 +161,29 @@ int loadFITSHeader(fitsfile *file, ImageInfoData &info)
var = vald;
else if(status == VALUE_UNDEFINED)
var = QVariant();
else if(string == "T" || string == "F")
var = string == "T";
else
var = strval;
var = string;
status = 0;
info.fitsHeader.append({key, var, comm});
info.fitsHeader.append(FITSRecord(key, var, comm));
}
else
{
return status;
}
}
char *header = nullptr;
int nrec = 0;
const char *exclist[] = {"PV1_1", "PV1_2"};
fits_hdr2str(file, TRUE, (char**)exclist, 2, &header, &nrec, &status);
if(status == 0)
{
info.wcs = std::make_shared<WCSData>(naxes[0], naxes[1], header, nrec);
if(!info.wcs->valid())info.wcs.reset();
}
fits_free_memory(header, &status);
return status;
}
@@ -308,8 +326,10 @@ bool loadXISF(const QString &path, ImageInfoData &info, RawImage **image)
auto fitskeywords = xisf.ReadFITSKeywords();
for(auto fits : fitskeywords)
{
info.fitsHeader.append({fits.name.c_str(), fits.value.c_str(), fits.comment.c_str()});
info.fitsHeader.append(fits);
}
info.wcs = std::make_shared<WCSData>(xisf.ImageInfo().width, xisf.ImageInfo().height, info.fitsHeader);
if(!info.wcs->valid())info.wcs.reset();
if(floatType && bps == 32)
return loadPCLImage<float, pcl::FImage, CV_32F>(xisf, image);
@@ -322,7 +342,6 @@ bool loadXISF(const QString &path, ImageInfoData &info, RawImage **image)
case 16:
return loadPCLImage<uint16_t, pcl::UInt16Image, CV_16U>(xisf, image);
}
}
}
catch (pcl::Error err)
@@ -483,8 +502,10 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
auto fitskeywords = xisf.ReadFITSKeywords();
for(auto fits : fitskeywords)
{
info.fitsHeader.append({fits.name.c_str(), fits.value.c_str(), fits.comment.c_str()});
info.fitsHeader.append(fits);
}
info.wcs = std::make_shared<WCSData>(xisf.ImageInfo().width, xisf.ImageInfo().height, info.fitsHeader);
if(!info.wcs->valid())info.wcs.reset();
}
catch (pcl::Error err)
{
@@ -607,7 +628,7 @@ void ConvertRunable::run()
pcl::FITSKeywordArray fitskeywords;
for(auto &record : imageinfo.fitsHeader)
{
pcl::FITSHeaderKeyword key(pcl::IsoString(record.key.data()), pcl::IsoString(record.value.toString().toLatin1().data()), pcl::IsoString(record.comment.data()));
pcl::FITSHeaderKeyword key(pcl::IsoString(record.key.data()), pcl::IsoString(record.valueToByteArray().data()), pcl::IsoString(record.comment.data()));
fitskeywords.Append(key);
}
pcl::XISFWriter xisf;
+5
View File
@@ -2,9 +2,14 @@
#include <QApplication>
#include <QSurfaceFormat>
#include <QTranslator>
#include <stdlib.h>
int main(int argc, char *argv[])
{
#ifdef __linux__
setenv("LC_NUMERIC", "C", 1);
#endif
QSurfaceFormat format;
format.setMajorVersion(3);
format.setMinorVersion(3);
+17 -6
View File
@@ -15,9 +15,11 @@
#include <QSettings>
#include <QCoreApplication>
#include <QThreadPool>
#include <QStatusBar>
#include "loadrunable.h"
#include "markedfiles.h"
#include "about.h"
#include "statusbar.h"
#ifdef __linux__
#include <sys/ioctl.h>
@@ -37,10 +39,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
infoDock->setWidget(m_info);
infoDock->setObjectName("infoDock");
addDockWidget(Qt::LeftDockWidgetArea, infoDock);
//m_image = new ImageScrollArea(this);
//m_image->resize(0,0);
//setCentralWidget(m_image);
resize(800, 600);
setStatusBar(new QStatusBar(this));
m_database = new Database(this);
if(!m_database->init())
@@ -49,13 +49,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
m_imageGL = new ImageScrollAreaGL(m_database, this);
setCentralWidget(m_imageGL);
StatusBar *statusBar = new StatusBar(this);
setStatusBar(statusBar);
connect(m_imageGL->imageWidget(), &ImageWidget::status, statusBar, &StatusBar::newStatus);
m_stretchPanel = new StretchToolbar(this);
connect(m_stretchPanel, SIGNAL(paramChanged(float,float,float)), m_imageGL->imageWidget(), SLOT(setMTFParams(float,float,float)));
connect(m_stretchPanel, &StretchToolbar::autoStretch, [&](){ m_stretchPanel->stretchImage(m_ringList->currentImage().get()); });
connect(m_stretchPanel, &StretchToolbar::invert, m_imageGL->imageWidget(), &ImageWidget::invert);
connect(m_stretchPanel, &StretchToolbar::superPixel, m_imageGL->imageWidget(), &ImageWidget::superPixel);
m_ringList = new ImageRingList(this);
m_ringList = new ImageRingList(m_database, this);
m_filesystem = new FilesystemWidget(m_ringList, this);
connect(m_filesystem, SIGNAL(fileSelected(int)), this, SLOT(loadFile(int)));
@@ -216,9 +220,11 @@ 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:
@@ -336,10 +342,9 @@ void MainWindow::socketNotify()
void MainWindow::pixmapLoaded(Image *image)
{
//m_image->setImage(image->pixmap());
if(image->rawImage())
{
m_imageGL->setImage(image->rawImage(), image->number());
m_imageGL->setImage(image);
}
}
@@ -435,7 +440,10 @@ void MainWindow::markImage()
{
QString file = ptr->name();
if(!file.isEmpty())
{
m_database->mark(file);
m_ringList->updateMark();
}
updateWindowTitle();
}
@@ -448,7 +456,10 @@ void MainWindow::unmarkImage()
{
QString file = ptr->name();
if(!file.isEmpty())
{
m_database->unmark(file);
m_ringList->updateMark();
}
updateWindowTitle();
}
+81 -1
View File
@@ -16,6 +16,8 @@ RawImage::ImgType CV2Type(int cvtype)
return RawImage::UINT8C4;
case CV_16UC3:
return RawImage::UINT16C3;
case CV_16UC4:
return RawImage::UINT16C4;
case CV_32FC3:
return RawImage::FLOAT32C3;
default:
@@ -39,6 +41,8 @@ int Type2CV(RawImage::ImgType type)
return CV_8UC4;
case RawImage::UINT16C3:
return CV_16UC3;
case RawImage::UINT16C4:
return CV_16UC4;
case RawImage::FLOAT32C3:
return CV_32FC3;
case RawImage::UNKNOWN:
@@ -84,6 +88,27 @@ RawImage::RawImage(const QImage &img)
m_img.create(img.height(), img.width(), CV_8UC4);
for(int i=0; i<img.height(); i++)
std::memcpy(m_img.ptr(i), img.scanLine(i), img.width()*4);
cv::cvtColor(m_img, m_img, cv::COLOR_BGRA2RGB);
}
else if(img.format() == QImage::Format_ARGB32)
{
m_img.create(img.height(), img.width(), CV_8UC4);
for(int i=0; i<img.height(); i++)
std::memcpy(m_img.ptr(i), img.scanLine(i), img.width()*4);
cv::cvtColor(m_img, m_img, cv::COLOR_BGRA2RGBA);
}
else if(img.format() == QImage::Format_RGBX64)
{
m_img.create(img.height(), img.width(), CV_16UC4);
for(int i=0; i<img.height(); i++)
std::memcpy(m_img.ptr(i), img.scanLine(i), img.width()*8);
cv::cvtColor(m_img, m_img, cv::COLOR_RGBA2RGB);
}
else if(img.format() == QImage::Format_RGBA64)
{
m_img.create(img.height(), img.width(), CV_16UC4);
for(int i=0; i<img.height(); i++)
std::memcpy(m_img.ptr(i), img.scanLine(i), img.width()*8);
}
else
{
@@ -284,7 +309,7 @@ void RawImage::convertToThumbnail()
if(m_img.channels() == 1)
cv::cvtColor(m_img, m_img, cv::COLOR_GRAY2RGB);
if(m_img.channels() == 4)
cv::cvtColor(m_img, m_img, cv::COLOR_BGRA2RGB);
cv::cvtColor(m_img, m_img, cv::COLOR_RGBA2RGB);
cv::Size dsize(THUMB_SIZE, THUMB_SIZE);
cv::resize(m_img, m_img, dsize, 0, 0, cv::INTER_NEAREST);
}
@@ -298,3 +323,58 @@ const cv::Mat& RawImage::mat() const
{
return m_img;
}
bool RawImage::pixel(int x, int y, QVector3D &rgb) const
{
if(x < 0 || y < 0 || x >= (int)width() || y >= (int)height())return false;
switch(m_img.type())
{
case CV_8U:
{
uint8_t v = m_img.at<uint8_t>(y, x);
rgb = QVector3D(v, v, v);
break;
}
case CV_16U:
{
uint16_t v = m_img.at<uint16_t>(y, x);
rgb = QVector3D(v, v, v);
break;
}
case CV_32F:
{
float v = m_img.at<float>(y, x);
rgb = QVector3D(v, v, v);
break;
}
case CV_8UC3:
{
cv::Vec3b v = m_img.at<cv::Vec3b>(y, x);
rgb = QVector3D(v[0], v[1], v[2]);
break;
}
case CV_8UC4:
{
cv::Vec4b v = m_img.at<cv::Vec4b>(y, x);
rgb = QVector3D(v[0], v[1], v[2]);
break;
}
case CV_16UC3:
{
cv::Vec3w v = m_img.at<cv::Vec3w>(y, x);
rgb = QVector3D(v[0], v[1], v[2]);
break;
}
case CV_32FC3:
{
cv::Vec3f v = m_img.at<cv::Vec3f>(y, x);
rgb = QVector3D(v[0], v[1], v[2]);
break;
}
default:
rgb = QVector3D(0, 0, 0);
break;
}
return true;
}
+3
View File
@@ -8,6 +8,7 @@
#include <memory.h>
#include <opencv2/imgproc.hpp>
#include <QImage>
#include <QVector3D>
const int THUMB_SIZE = 128;
const int THUMB_SIZE_BORDER = 138;
@@ -56,6 +57,7 @@ public:
UINT8C3,
UINT8C4,
UINT16C3,
UINT16C4,
FLOAT32C3,
UNKNOWN,
};
@@ -81,6 +83,7 @@ public:
void convertToThumbnail();
float thumbAspect() const;
const cv::Mat& mat() const;
bool pixel(int x, int y, QVector3D &rgb) const;
};
#endif // RAWIMAGE_H
+4 -6
View File
@@ -1,10 +1,4 @@
<RCC>
<qresource prefix="/shaders">
<file>image.frag</file>
<file>image.vert</file>
<file>thumb.frag</file>
<file>thumb.vert</file>
</qresource>
<qresource prefix="/">
<file>invert.png</file>
<file>nuke.png</file>
@@ -18,6 +12,10 @@
<file>about/filter.png</file>
<file>about/stretch-panel.png</file>
<file>translations/tenmon_fr.qm</file>
<file>shaders/image.frag</file>
<file>shaders/image.vert</file>
<file>shaders/thumb.frag</file>
<file>shaders/thumb.vert</file>
</qresource>
<qresource lang="en" prefix="/">
<file alias="help">about/help_en</file>
+3 -1
View File
@@ -23,5 +23,7 @@ void main(void)
if(invert)color = vec4(1.0) - color;
if(any(lessThan(qt_TexCoord0, vec2(0.0))) || any(greaterThan(qt_TexCoord0, vec2(1.0))))
color = vec4(0.0);
color = vec4(0.0, 0.0, 0.0, 1.0);
color.a = 1.0;
}
View File
+1
View File
@@ -18,4 +18,5 @@ void main(void)
color = texture(qt_Texture0, qt_TexCoord0);
color = MTF(color, mtf_param);
if(invert)color = vec4(1.0) - color;
color.a = 1.0;
}
View File
+23
View File
@@ -0,0 +1,23 @@
#include "statusbar.h"
#include <QFontMetrics>
StatusBar::StatusBar(QWidget *parent) : QStatusBar(parent)
{
m_value = new QLabel(this);
m_pixelCoords = new QLabel(this);
m_celestianCoords = new QLabel(this);
m_value->setMinimumWidth(m_value->fontMetrics().horizontalAdvance("R:65536 G:65536 B:65536 "));
m_pixelCoords->setMinimumWidth(m_pixelCoords->fontMetrics().horizontalAdvance("X:65536 Y:65536 "));
m_celestianCoords->setMinimumWidth(m_celestianCoords->fontMetrics().horizontalAdvance("RA: 00h00m00s DEC: 00° 00' 00\" "));
addPermanentWidget(m_value);
addPermanentWidget(m_pixelCoords);
addPermanentWidget(m_celestianCoords);
}
void StatusBar::newStatus(const QString &value, const QString &pixelCoords, const QString &celestialCoords)
{
m_value->setText(value);
m_pixelCoords->setText(pixelCoords);
m_celestianCoords->setText(celestialCoords);
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef STATUSBAR_H
#define STATUSBAR_H
#include <QStatusBar>
#include <QLabel>
class StatusBar : public QStatusBar
{
Q_OBJECT
QLabel *m_value;
QLabel *m_pixelCoords;
QLabel *m_celestianCoords;
public:
explicit StatusBar(QWidget *parent = nullptr);
public slots:
void newStatus(const QString &value, const QString &pixelCoords, const QString &celestialCoords);
};
#endif // STATUSBAR_H
BIN
View File
Binary file not shown.
Binary file not shown.
+28 -1
View File
@@ -23,6 +23,17 @@
<translation>Filter</translation>
</message>
</context>
<context>
<name>DatabaseTableView</name>
<message>
<source>Mark</source>
<translation>Mark</translation>
</message>
<message>
<source>Unmark</source>
<translation>Unmark</translation>
</message>
</context>
<context>
<name>FITSFileModel</name>
<message>
@@ -108,6 +119,18 @@
<source>Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed.</source>
<translation>Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed.</translation>
</message>
<message>
<source>L:%1</source>
<translation>L:%1</translation>
</message>
<message>
<source>X:%3 Y:%4</source>
<translation>X:%3 Y:%4</translation>
</message>
<message>
<source>R:%1 G:%2 B:%3</source>
<translation>R:%1 G:%2 B:%3</translation>
</message>
</context>
<context>
<name>MainWindow</name>
@@ -269,7 +292,7 @@
</message>
<message>
<source>Images (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.FIT *.FITS *.XISF)</source>
<translation>Images (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.FIT *.FITS *.XISF)</translation>
<translation type="vanished">Images (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.FIT *.FITS *.XISF)</translation>
</message>
<message>
<source>Indexing FITS files</source>
@@ -295,6 +318,10 @@
<source>Star finder</source>
<translation>Star finder</translation>
</message>
<message>
<source>Images (*.jpg *.jpeg *.png *.cr2 *.nef *.dng *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.NEF *.DNG *.FIT *.FITS *.XISF)</source>
<translation>Images (*.jpg *.jpeg *.png *.cr2 *.nef *.dng *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.NEF *.DNG *.FIT *.FITS *.XISF)</translation>
</message>
</context>
<context>
<name>MarkedFiles</name>
Binary file not shown.
+28 -1
View File
@@ -23,6 +23,17 @@
<translation>Filtre</translation>
</message>
</context>
<context>
<name>DatabaseTableView</name>
<message>
<source>Mark</source>
<translation>Marquer</translation>
</message>
<message>
<source>Unmark</source>
<translation>Décocher</translation>
</message>
</context>
<context>
<name>FITSFileModel</name>
<message>
@@ -108,6 +119,18 @@
<source>Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed.</source>
<translation>Impossible d&apos;initialiser le contexte OpenGL 3.3. Assurez-vous que le pilote GPU approprié est installé.</translation>
</message>
<message>
<source>L:%1</source>
<translation>L:%1</translation>
</message>
<message>
<source>X:%3 Y:%4</source>
<translation>X:%3 Y:%4</translation>
</message>
<message>
<source>R:%1 G:%2 B:%3</source>
<translation>R:%1 G:%2 B:%3</translation>
</message>
</context>
<context>
<name>MainWindow</name>
@@ -269,7 +292,7 @@
</message>
<message>
<source>Images (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.FIT *.FITS *.XISF)</source>
<translation>Images (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.FIT *.FITS *.XISF)</translation>
<translation type="vanished">Images (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.FIT *.FITS *.XISF)</translation>
</message>
<message>
<source>Indexing FITS files</source>
@@ -295,6 +318,10 @@
<source>Star finder</source>
<translation>Détecteur d&apos;étoiles</translation>
</message>
<message>
<source>Images (*.jpg *.jpeg *.png *.cr2 *.nef *.dng *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.NEF *.DNG *.FIT *.FITS *.XISF)</source>
<translation>Images (*.jpg *.jpeg *.png *.cr2 *.nef *.dng *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.NEF *.DNG *.FIT *.FITS *.XISF)</translation>
</message>
</context>
<context>
<name>MarkedFiles</name>
Binary file not shown.
+28 -1
View File
@@ -24,6 +24,17 @@
<translation>Filter</translation>
</message>
</context>
<context>
<name>DatabaseTableView</name>
<message>
<source>Mark</source>
<translation>Označiť</translation>
</message>
<message>
<source>Unmark</source>
<translation>Odznačiť</translation>
</message>
</context>
<context>
<name>FITSFileModel</name>
<message>
@@ -109,6 +120,18 @@
<source>Could not initialize OpenGL 3.3 context. Ensure that proper GPU driver is installed.</source>
<translation>Nepodarilo sa incializovať OpenGL 3.3 context. Ubezpečte sa že nainštalované ovládače pre GPU.</translation>
</message>
<message>
<source>L:%1</source>
<translation>L:%1</translation>
</message>
<message>
<source>X:%3 Y:%4</source>
<translation>X:%3 Y:%4</translation>
</message>
<message>
<source>R:%1 G:%2 B:%3</source>
<translation>R:%1 G:%2 B:%3</translation>
</message>
</context>
<context>
<name>MainWindow</name>
@@ -282,7 +305,7 @@
</message>
<message>
<source>Images (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.FIT *.FITS *.XISF)</source>
<translation>Obrázky (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.FIT *.FITS *.XISF)</translation>
<translation type="vanished">Obrázky (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.FIT *.FITS *.XISF)</translation>
</message>
<message>
<source>Indexing FITS files</source>
@@ -308,6 +331,10 @@
<source>Star finder</source>
<translation>Vyhľadávač hviezd</translation>
</message>
<message>
<source>Images (*.jpg *.jpeg *.png *.cr2 *.nef *.dng *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.NEF *.DNG *.FIT *.FITS *.XISF)</source>
<translation>Obrázky (*.jpg *.jpeg *.png *.cr2 *.nef *.dng *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.NEF *.DNG *.FIT *.FITS *.XISF)</translation>
</message>
</context>
<context>
<name>MarkedFiles</name>