Skip to content

Commit

Permalink
Merge branch 'dev' into pam/1377_save_image
Browse files Browse the repository at this point in the history
  • Loading branch information
pford authored Jul 22, 2024
2 parents 9a8edd9 + 010335c commit 5b8c416
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 110 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
* Support PV image generation along a polyline region ([#1341](https://github.com/CARTAvis/carta-backend/issues/1341)).

### Fixed
* Fixed crash when loading non-image HDU by URL ([#1365](https://github.com/CARTAvis/carta-backend/issues/1365)).
* Fix crash when parsing FITS header long value ([#1366](https://github.com/CARTAvis/carta-backend/issues/1366)).
* Fix incorrect parsing of SPECSYS value for ATCA FITS header ([#1375](https://github.com/CARTAvis/carta-backend/issues/1375)).
* Fix hdf5 image distortion after animation stops ([#1368](https://github.com/CARTAvis/carta-backend/issues/1368)).
* Fix save image bug which could cause directory overwrite or deletion ([#1377](https://github.com/CARTAvis/carta-backend/issues/1377)).
* Fix matched polygon region approximation crash ([#1383](https://github.com/CARTAvis/carta-backend/issues/1383)).
* Fix save image/export regions bug which could cause directory overwrite or deletion ([#1377](https://github.com/CARTAvis/carta-backend/issues/1377)).

### Changed
* Move the loader cache to separate files ([#1021](https://github.com/CARTAvis/carta-backend/issues/1021)).
Expand Down
63 changes: 34 additions & 29 deletions src/HttpServer/HttpServer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ uint32_t HttpServer::_scripting_request_id = 0;

HttpServer::HttpServer(std::shared_ptr<SessionManager> session_manager, fs::path root_folder, fs::path user_directory,
std::string auth_token, bool read_only_mode, bool enable_frontend, bool enable_database, bool enable_scripting,
bool enable_runtime_config)
bool enable_runtime_config, std::string url_prefix)
: _session_manager(session_manager),
_http_root_folder(root_folder),
_auth_token(auth_token),
Expand All @@ -45,7 +45,8 @@ HttpServer::HttpServer(std::shared_ptr<SessionManager> session_manager, fs::path
_enable_frontend(enable_frontend),
_enable_database(enable_database),
_enable_scripting(enable_scripting),
_enable_runtime_config(enable_runtime_config) {
_enable_runtime_config(enable_runtime_config),
_url_prefix(url_prefix) {
if (_enable_frontend && !root_folder.empty()) {
_frontend_found = IsValidFrontendFolder(root_folder);

Expand All @@ -61,70 +62,74 @@ void HttpServer::RegisterRoutes() {
uWS::App& app = _session_manager->App();

if (_enable_scripting) {
app.post("/api/scripting/action", [&](auto res, auto req) { HandleScriptingAction(res, req); });
app.post(fmt::format("{}/api/scripting/action", _url_prefix), [&](auto res, auto req) { HandleScriptingAction(res, req); });
} else {
app.post("/api/scripting/action", [&](auto res, auto req) { NotImplemented(res, req); });
app.post(fmt::format("{}/api/scripting/action", _url_prefix), [&](auto res, auto req) { NotImplemented(res, req); });
}

if (_enable_database) {
// Dynamic routes for preferences, layouts, snippets and workspaces
app.get("/api/database/preferences", [&](auto res, auto req) { HandleGetPreferences(res, req); });
app.put("/api/database/preferences", [&](auto res, auto req) { HandleSetPreferences(res, req); });
app.del("/api/database/preferences", [&](auto res, auto req) { HandleClearPreferences(res, req); });
app.get(fmt::format("{}/api/database/preferences", _url_prefix), [&](auto res, auto req) { HandleGetPreferences(res, req); });
app.put(fmt::format("{}/api/database/preferences", _url_prefix), [&](auto res, auto req) { HandleSetPreferences(res, req); });
app.del(fmt::format("{}/api/database/preferences", _url_prefix), [&](auto res, auto req) { HandleClearPreferences(res, req); });

for (const auto& elem : SCHEMA_URLS) {
const auto& object_type = elem.first;
app.get(fmt::format("/api/database/list/{}s", object_type),
app.get(fmt::format("{}/api/database/list/{}s", _url_prefix, object_type),
[&](auto res, auto req) { HandleGetObjectList(object_type, res, req); });
app.get(fmt::format("/api/database/{}s", object_type), [&](auto res, auto req) { HandleGetObjects(object_type, res, req); });
app.get(
fmt::format("/api/database/{}/:name", object_type), [&](auto res, auto req) { HandleGetObject(object_type, res, req); });
app.put(fmt::format("/api/database/{}", object_type), [&](auto res, auto req) { HandleSetObject(object_type, res, req); });
app.del(fmt::format("/api/database/{}", object_type), [&](auto res, auto req) { HandleClearObject(object_type, res, req); });
app.get(fmt::format("{}/api/database/{}s", _url_prefix, object_type),
[&](auto res, auto req) { HandleGetObjects(object_type, res, req); });
app.get(fmt::format("{}/api/database/{}/:name", _url_prefix, object_type),
[&](auto res, auto req) { HandleGetObject(object_type, res, req); });
app.put(fmt::format("{}/api/database/{}", _url_prefix, object_type),
[&](auto res, auto req) { HandleSetObject(object_type, res, req); });
app.del(fmt::format("{}/api/database/{}", _url_prefix, object_type),
[&](auto res, auto req) { HandleClearObject(object_type, res, req); });
}
} else {
app.get("/api/database/*", [&](auto res, auto req) { NotImplemented(res, req); });
app.put("/api/database/*", [&](auto res, auto req) { NotImplemented(res, req); });
app.del("/api/database/*", [&](auto res, auto req) { NotImplemented(res, req); });
app.get(fmt::format("{}/api/database/*", _url_prefix), [&](auto res, auto req) { NotImplemented(res, req); });
app.put(fmt::format("{}/api/database/*", _url_prefix), [&](auto res, auto req) { NotImplemented(res, req); });
app.del(fmt::format("{}/api/database/*", _url_prefix), [&](auto res, auto req) { NotImplemented(res, req); });
}

if (_enable_frontend) {
if (_enable_runtime_config) {
app.get("/config", [&](auto res, auto req) { HandleGetConfig(res, req); });
app.get(fmt::format("{}/config", _url_prefix), [&](auto res, auto req) { HandleGetConfig(res, req); });
} else {
app.get("/config", [&](auto res, auto req) { DefaultSuccess(res, req); });
app.get(fmt::format("{}/config", _url_prefix), [&](auto res, auto req) { DefaultSuccess(res, req); });
}
// Static routes for all other files
app.get("/*", [&](Res* res, Req* req) { HandleStaticRequest(res, req); });
app.get(fmt::format("{}/*", _url_prefix), [&](Res* res, Req* req) { HandleStaticRequest(res, req); });
} else {
app.get("/*", [&](auto res, auto req) { NotImplemented(res, req); });
app.get(fmt::format("{}/*", _url_prefix), [&](auto res, auto req) { NotImplemented(res, req); });
}

// CORS support for the API
app.options("/api/*", [&](auto res, auto req) {
app.options(fmt::format("{}/api/*", _url_prefix), [&](auto res, auto req) {
AddCorsHeaders(res);
res->end();
});
}

void HttpServer::HandleGetConfig(Res* res, Req* req) {
json runtime_config = {{"apiAddress", "/api"}};
json runtime_config = {{"apiAddress", fmt::format("{}/api", _url_prefix)}};
res->writeStatus(HTTP_200);
res->writeHeader("Content-Type", "application/json");
res->end(runtime_config.dump());
}

void HttpServer::HandleStaticRequest(Res* res, Req* req) {
std::string_view url = req->getUrl();
std::string url(req->getUrl());
fs::path path = _http_root_folder;
if (url.empty() || url == "/") {

// Trim prefix and any number of slashes before and after it
std::regex prefix(fmt::format("^/*{}/*", _url_prefix));
url = std::regex_replace(url, prefix, "");

if (url.empty()) {
path /= "index.html";
} else {
// Trim all leading '/'
while (url.size() && url[0] == '/') {
url = url.substr(1);
}
path /= std::string(url);
path /= url;
}

std::error_code error_code;
Expand Down
3 changes: 2 additions & 1 deletion src/HttpServer/HttpServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class HttpServer {
public:
HttpServer(std::shared_ptr<SessionManager> session_manager, fs::path root_folder, fs::path user_directory, std::string auth_token,
bool read_only_mode = false, bool enable_frontend = true, bool enable_database = true, bool enable_scripting = false,
bool enable_runtime_config = true);
bool enable_runtime_config = true, std::string url_prefix = "");
bool CanServeFrontend() {
return _frontend_found;
}
Expand Down Expand Up @@ -96,6 +96,7 @@ class HttpServer {
bool _enable_database;
bool _enable_scripting;
bool _enable_runtime_config;
std::string _url_prefix;
std::shared_ptr<SessionManager> _session_manager;
static uint32_t _scripting_request_id;
};
Expand Down
57 changes: 38 additions & 19 deletions src/ImageGenerators/PvGenerator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ void PvGenerator::SetFileName(int index, const std::string& filename, bool is_pr
}

bool PvGenerator::GetPvImage(const std::shared_ptr<Frame>& frame, const casacore::Matrix<float>& pv_data, casacore::IPosition& pv_shape,
const casacore::Quantity& offset_increment, int start_chan, int stokes, bool reverse, GeneratedImage& pv_image, std::string& message) {
PositionAxisType axis_type, const casacore::Quantity& position_increment, int start_chan, int stokes, bool reverse,
GeneratedImage& pv_image, std::string& message) {
// Create PV image with input data.
auto input_csys = frame->CoordinateSystem();
if (!input_csys->hasSpectralAxis()) {
Expand All @@ -32,12 +33,26 @@ bool PvGenerator::GetPvImage(const std::shared_ptr<Frame>& frame, const casacore
return false;
}

// Convert spectral reference pixel value to world coordinates
// Position axis
int linear_axis = (reverse ? 1 : 0);
std::string position_name;
double position_refval(0.0), position_refpix(0.0);
if (axis_type == OFFSET) {
// refval 0 at center pixel
position_name = "Offset";
position_refpix = (pv_shape[linear_axis] - 1) / 2;
} else {
// refval 0 at first pixel
position_name = "Distance";
}

// Spectral axis: Convert spectral reference pixel value to world coordinates
double spectral_refval, spectral_pixval(start_chan);
input_csys->spectralCoordinate().toWorld(spectral_refval, spectral_pixval);

// Create casacore::TempImage coordinate system and other image info
auto image = SetupPvImage(frame->GetImage(), input_csys, pv_shape, stokes, offset_increment, spectral_refval, reverse, message);
auto image = SetupPvImage(frame->GetImage(), input_csys, pv_shape, position_name, position_increment, position_refval, position_refpix,
spectral_refval, stokes, reverse, message);
if (!image) {
message = "PV image setup failed.";
return false;
Expand Down Expand Up @@ -73,10 +88,12 @@ void PvGenerator::SetPvImageName(const std::string& filename, int index, bool is

std::shared_ptr<casacore::ImageInterface<casacore::Float>> PvGenerator::SetupPvImage(
const std::shared_ptr<casacore::ImageInterface<float>>& input_image, const std::shared_ptr<casacore::CoordinateSystem>& input_csys,
casacore::IPosition& pv_shape, int stokes, const casacore::Quantity& offset_increment, double spectral_refval, bool reverse,
std::string& message) {
casacore::IPosition& pv_shape, const std::string& position_name, const casacore::Quantity& position_increment, double position_refval,
double position_refpix, double spectral_refval, int stokes, bool reverse, std::string& message) {
// Create temporary image (no data) using input image. Return casacore::TempImage.
casacore::CoordinateSystem pv_csys = GetPvCoordinateSystem(input_csys, pv_shape, stokes, offset_increment, spectral_refval, reverse);
// Position axis
casacore::CoordinateSystem pv_csys = GetPvCoordinateSystem(
input_csys, pv_shape, position_name, position_increment, position_refval, position_refpix, spectral_refval, stokes, reverse);

std::shared_ptr<casacore::ImageInterface<casacore::Float>> image(
new casacore::TempImage<casacore::Float>(casacore::TiledShape(pv_shape), pv_csys));
Expand All @@ -96,35 +113,37 @@ std::shared_ptr<casacore::ImageInterface<casacore::Float>> PvGenerator::SetupPvI
}

casacore::CoordinateSystem PvGenerator::GetPvCoordinateSystem(const std::shared_ptr<casacore::CoordinateSystem>& input_csys,
casacore::IPosition& pv_shape, int stokes, const casacore::Quantity& offset_increment, double spectral_refval, bool reverse) {
casacore::IPosition& pv_shape, const std::string& position_name, const casacore::Quantity& position_increment, double position_refval,
double position_refpix, double spectral_refval, int stokes, bool reverse) {
// Set PV coordinate system with LinearCoordinate and input coordinates for spectral and stokes
casacore::CoordinateSystem pv_csys;
int offset_axis = reverse ? 1 : 0;

// Add linear coordinate (offset); needs to have 2 axes or pc matrix will fail in wcslib.
// Add linear coordinate (offset or distance); needs to have 2 axes or pc matrix will fail in wcslib.
// Will remove degenerate linear axis below
casacore::Vector<casacore::String> name(2, "Offset");
casacore::Vector<casacore::String> unit(2, offset_increment.getUnit());
casacore::Vector<casacore::Double> crval(2, 0.0); // center offset is 0
casacore::Vector<casacore::Double> inc(2, offset_increment.getValue());
casacore::Vector<casacore::String> name(2, position_name);
casacore::Vector<casacore::String> unit(2, position_increment.getUnit());
casacore::Vector<casacore::Double> refval(2, position_refval);
casacore::Vector<casacore::Double> inc(2, position_increment.getValue());
casacore::Matrix<casacore::Double> pc(2, 2, 1);
pc(0, 1) = 0.0;
pc(1, 0) = 0.0;
casacore::Vector<casacore::Double> crpix(2, (pv_shape[offset_axis] - 1) / 2);
casacore::LinearCoordinate linear_coord(name, unit, crval, inc, pc, crpix);
casacore::Vector<casacore::Double> refpix(2, position_refpix);
casacore::LinearCoordinate linear_coord(name, unit, refval, inc, pc, refpix);

// Set spectral coordinate
auto& input_spectral_coord = input_csys->spectralCoordinate();
auto freq_type = input_spectral_coord.frequencySystem();
auto freq_inc = input_spectral_coord.increment()(0);
auto rest_freq = input_spectral_coord.restFrequency();
casacore::Double refpix(0.0);
casacore::SpectralCoordinate spectral_coord(freq_type, spectral_refval, freq_inc, refpix, rest_freq);
casacore::Double spectral_refpix(0.0);
casacore::SpectralCoordinate spectral_coord(freq_type, spectral_refval, freq_inc, spectral_refpix, rest_freq);

// Add offset and spectral coordinates
// Add offset and spectral coordinates in axis order
int linear_axis(0);
if (reverse) {
pv_csys.addCoordinate(spectral_coord);
pv_csys.addCoordinate(linear_coord);
linear_axis = 1;
} else {
pv_csys.addCoordinate(linear_coord);
pv_csys.addCoordinate(spectral_coord);
Expand All @@ -140,7 +159,7 @@ casacore::CoordinateSystem PvGenerator::GetPvCoordinateSystem(const std::shared_
}

// Remove second linear axis
pv_csys.removeWorldAxis(offset_axis + 1, 0.0);
pv_csys.removeWorldAxis(linear_axis + 1, 0.0);

pv_csys.setObsInfo(input_csys->obsInfo());

Expand Down
17 changes: 11 additions & 6 deletions src/ImageGenerators/PvGenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,32 @@ namespace carta {

class PvGenerator {
public:
enum PositionAxisType { OFFSET, DISTANCE };

PvGenerator();
~PvGenerator() = default;

// For PV generator (not preview)
void SetFileName(int index, const std::string& filename, bool is_preview = false);

// Create generated PV image from input data. If reverse, [spectral, offset] instead of normal [offset, spectral].
// Create generated PV image from input data.
// cut_region_type determines position axis as offset from center (line) or distance from beginning (polyline).
// reverse determines order of position and spectral axes.
// Returns generated image and success, with message if failure.
bool GetPvImage(const std::shared_ptr<Frame>& frame, const casacore::Matrix<float>& pv_data, casacore::IPosition& pv_shape,
const casacore::Quantity& offset_increment, int start_chan, int stokes, bool reverse, GeneratedImage& pv_image,
std::string& message);
PositionAxisType axis_type, const casacore::Quantity& position_increment, int start_chan, int stokes, bool reverse,
GeneratedImage& pv_image, std::string& message);

private:
void SetPvImageName(const std::string& filename, int index, bool is_preview);

std::shared_ptr<casacore::ImageInterface<casacore::Float>> SetupPvImage(
const std::shared_ptr<casacore::ImageInterface<float>>& input_image, const std::shared_ptr<casacore::CoordinateSystem>& input_csys,
casacore::IPosition& pv_shape, int stokes, const casacore::Quantity& offset_increment, double spectral_refval, bool reverse,
std::string& message);
casacore::IPosition& pv_shape, const std::string& position_name, const casacore::Quantity& position_increment,
double position_refval, double position_refpix, double spectral_refval, int stokes, bool reverse, std::string& message);
casacore::CoordinateSystem GetPvCoordinateSystem(const std::shared_ptr<casacore::CoordinateSystem>& input_csys,
casacore::IPosition& pv_shape, int stokes, const casacore::Quantity& offset_increment, double spectral_refval, bool reverse);
casacore::IPosition& pv_shape, const std::string& position_name, const casacore::Quantity& position_increment,
double position_refval, double position_refpix, double spectral_refval, int stokes, bool reverse);

// GeneratedImage parameters
int _file_id;
Expand Down
Loading

0 comments on commit 5b8c416

Please sign in to comment.