diff --git a/.gitignore b/.gitignore index df0f2d1ed4..62be281b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /cache/ /data/.internal/* !/data/.internal/readme.md +!/data/.internal/tf_presets /doc /documentation /ext/SGCT diff --git a/data/.internal/tf_presets/basic.txt b/data/.internal/tf_presets/basic.txt new file mode 100644 index 0000000000..a9688245bf --- /dev/null +++ b/data/.internal/tf_presets/basic.txt @@ -0,0 +1,11 @@ +width 1024 +lower 0.0 +upper 1.0 +mappingkey 0.15 250 0 0 0 +mappingkey 0.20 250 0 0 20 +mappingkey 0.35 250 20 0 20 +mappingkey 0.40 250 0 0 0 +mappingkey 0.60 250 0 0 0 +mappingkey 0.80 0 0 200 255 + +http://localhost:1337/enlil.png diff --git a/data/.internal/tf_presets/enlil_basic.txt b/data/.internal/tf_presets/enlil_basic.txt new file mode 100644 index 0000000000..a9688245bf --- /dev/null +++ b/data/.internal/tf_presets/enlil_basic.txt @@ -0,0 +1,11 @@ +width 1024 +lower 0.0 +upper 1.0 +mappingkey 0.15 250 0 0 0 +mappingkey 0.20 250 0 0 20 +mappingkey 0.35 250 20 0 20 +mappingkey 0.40 250 0 0 0 +mappingkey 0.60 250 0 0 0 +mappingkey 0.80 0 0 200 255 + +http://localhost:1337/enlil.png diff --git a/data/.internal/tf_presets/mas_mhd_original.txt b/data/.internal/tf_presets/mas_mhd_original.txt new file mode 100644 index 0000000000..130dae5cc7 --- /dev/null +++ b/data/.internal/tf_presets/mas_mhd_original.txt @@ -0,0 +1,20 @@ +width 2048 +lower 0.0 +upper 1.0 + +mappingkey 0.000001 50 50 250 2 +mappingkey 0.000015 250 250 250 250 +mappingkey 0.00025 70 170 255 250 +mappingkey 0.004 120 230 255 250 +mappingkey 0.0045 255 220 180 250 +mappingkey 0.06 255 150 120 250 +mappingkey 0.1 255 50 20 250 + +mappingkey 0.15 250 0 0 200 +mappingkey 0.20 250 0 0 200 +mappingkey 0.35 250 20 0 200 +mappingkey 0.40 250 0 0 200 +mappingkey 0.60 250 0 0 200 +mappingkey 0.80 250 0 0 255 + +http://localhost:1337/enlil.png diff --git a/data/.internal/tf_presets/mas_mhd_r_squared.txt b/data/.internal/tf_presets/mas_mhd_r_squared.txt new file mode 100644 index 0000000000..049b01865e --- /dev/null +++ b/data/.internal/tf_presets/mas_mhd_r_squared.txt @@ -0,0 +1,18 @@ +width 2048 +lower 0.0 +upper 1.0 + +mappingkey 0.005 0 54 135 0 +mappingkey 0.06 200 200 255 16 +mappingkey 0.1 220 230 255 200 +mappingkey 0.2 90 200 255 250 +mappingkey 0.3 90 180 255 250 +mappingkey 0.4 47 93 183 250 +mappingkey 0.5 92 177 242 250 +mappingkey 0.6 137 223 255 250 +mappingkey 0.7 94 255 214 250 +mappingkey 0.8 255 200 180 250 +mappingkey 0.9 249 199 150 250 +mappingkey 1.0 255 140 100 250 + +http://localhost:1337/mas_mhd_r_squared.png diff --git a/data/.internal/tf_presets/mas_mhd_temperature.txt b/data/.internal/tf_presets/mas_mhd_temperature.txt new file mode 100644 index 0000000000..5c8868f176 --- /dev/null +++ b/data/.internal/tf_presets/mas_mhd_temperature.txt @@ -0,0 +1,18 @@ +width 2048 +lower 0.0 +upper 1.0 + +mappingkey 0.005 0 54 135 0 +mappingkey 0.06 255 230 0 0 +mappingkey 0.1 255 210 0 5 +mappingkey 0.2 255 100 0 200 +mappingkey 0.3 255 90 0 200 +mappingkey 0.4 255 80 0 200 +mappingkey 0.5 255 70 0 200 +mappingkey 0.6 255 60 0 200 +mappingkey 0.7 255 50 0 200 +mappingkey 0.8 255 40 0 200 +mappingkey 0.9 255 30 0 200 +mappingkey 1.0 255 20 0 200 + +http://localhost:1337/mas_mhd_temperature.png diff --git a/data/.internal/tf_presets/mas_mhd_velocity.txt b/data/.internal/tf_presets/mas_mhd_velocity.txt new file mode 100644 index 0000000000..e7ea3eac54 --- /dev/null +++ b/data/.internal/tf_presets/mas_mhd_velocity.txt @@ -0,0 +1,18 @@ +width 2048 +lower 0.0 +upper 1.0 + +mappingkey 0.005 0 54 135 100 +mappingkey 0.06 0 67 168 95 +mappingkey 0.1 90 180 255 95 +mappingkey 0.2 90 180 255 95 +mappingkey 0.3 90 180 255 70 +mappingkey 0.4 47 93 183 50 +mappingkey 0.5 92 177 242 10 +mappingkey 0.6 137 223 255 9 +mappingkey 0.7 94 255 214 2 +mappingkey 0.8 255 200 114 0 +mappingkey 0.9 249 199 97 0 +mappingkey 1.0 255 140 48 0 + +http://localhost:1337/mas_mhd_velocity.png diff --git a/modules/dataloader/dataloadermodule.cpp b/modules/dataloader/dataloadermodule.cpp index 80ec3bc0f4..0608a6e59d 100644 --- a/modules/dataloader/dataloadermodule.cpp +++ b/modules/dataloader/dataloadermodule.cpp @@ -56,6 +56,18 @@ namespace { "List of fieldline items stored internally and ready to load", "This list contains names of fieldline data files converted from the CDF format" }; + + static const openspace::properties::Property::PropertyInfo TransferFunctionPresetsJSONInfo = { + "TransferFunctionPresetsJSON", + "JSON formatted string of a list of paths to transferfunction files and corresponding screenshot image paths", + "" + }; + + static const openspace::properties::Property::PropertyInfo ReadTransferFunctionPresetsInfo = { + "ReadTransferFunctionPresets", + "Trigger a read of the directory of transfer function presets and their screenshot image links", + "" + }; } namespace openspace { @@ -64,6 +76,8 @@ DataLoaderModule::DataLoaderModule() : OpenSpaceModule(Name) , _showInternalVolumesTrigger(ShowInternalVolumesInfo) , _volumeDataItems(VolumesInfo) + , _transferFunctionPresetsJSON(TransferFunctionPresetsJSONInfo) + , _readTransferFunctionPresets(ReadTransferFunctionPresetsInfo) { _showInternalVolumesTrigger.onChange([this](){ // showInternalDataType(DataTypes.volume); @@ -71,8 +85,14 @@ DataLoaderModule::DataLoaderModule() _loader->createInternalDataItemProperties(); }); + _readTransferFunctionPresets.onChange([this](){ + setTransferFunctionPresets(); + }); + addProperty(_volumeDataItems); + addProperty(_transferFunctionPresetsJSON); addProperty(_showInternalVolumesTrigger); + addProperty(_readTransferFunctionPresets); } DataLoaderModule::~DataLoaderModule() {} @@ -87,7 +107,6 @@ void DataLoaderModule::internalInitialize(const ghoul::Dictionary&) { } void DataLoaderModule::setDataDirectoryRead(bool isRead) { - LDEBUG("Called a module function"); _dataDirectoryIsRead = isRead; } @@ -116,6 +135,11 @@ void DataLoaderModule::setVolumeDataItems(std::vector items) { _volumeDataItems = items; } +void DataLoaderModule::setTransferFunctionPresets() { + const std::string presetsString = _reader->readTransferFunctionPresets(); + _transferFunctionPresetsJSON = presetsString; +} + openspace::dataloader::Reader* DataLoaderModule::reader() { return _reader.get(); } diff --git a/modules/dataloader/dataloadermodule.h b/modules/dataloader/dataloadermodule.h index 42984dd48a..2b53ce4332 100644 --- a/modules/dataloader/dataloadermodule.h +++ b/modules/dataloader/dataloadermodule.h @@ -28,6 +28,7 @@ #include #include // do we need this #include +#include #include namespace openspace::dataloader { @@ -61,6 +62,7 @@ public: std::vector volumeDataItems(); void setVolumeDataItems(std::vector items); + void setTransferFunctionPresets(); void loadDataItem(const std::string& absPathToItem); void uploadDataItem(const std::string& dictionaryString); @@ -77,7 +79,10 @@ private: std::unique_ptr _loader; properties::StringListProperty _volumeDataItems; + properties::StringProperty _transferFunctionPresetsJSON; + properties::TriggerProperty _showInternalVolumesTrigger; + properties::TriggerProperty _readTransferFunctionPresets; }; } // namespace openspace diff --git a/modules/dataloader/operators/loader.cpp b/modules/dataloader/operators/loader.cpp index bf79f03d6b..a0431c2040 100644 --- a/modules/dataloader/operators/loader.cpp +++ b/modules/dataloader/operators/loader.cpp @@ -47,6 +47,7 @@ #include #include +#include #include #ifdef _WIN32 @@ -84,6 +85,8 @@ constexpr const char* KeyTarget = "Target"; constexpr const char* KeyStaticTranslation = "StaticTranslation"; constexpr const char* KeyStaticScale = "StaticScale"; constexpr const char* KeyScale = "Scale"; +constexpr const char* KeyTransferFunctionPath = "TransferFunctionPath"; +constexpr const char* KeyTranslationValues = "TranslationValues"; const double DefaultStepSize = 0.02; const std::string DefaultGridType = "Spherical"; @@ -291,13 +294,9 @@ void Loader::goToFirstTimeStep(const std::string& absPathToItem) { } void Loader::processCurrentlySelectedUploadData(const std::string& dictionaryString) { - // Determine path to new volume item - + LINFO(dictionaryString); _currentVolumeConversionDictionary = ghoul::lua::loadDictionaryFromString(dictionaryString); - // Schedule tasks? How to loop through several CDF timesteps and run VolumeToRaw for each - // Create instance of KameleonVolumeToRaw and run - { std::thread t([&](){ // ??? won't work multithreaded @@ -312,31 +311,23 @@ void Loader::processCurrentlySelectedUploadData(const std::string& dictionaryStr // LDEBUG(itemPathBase); // itemPathBase += openspace::dataloader::helpers::getDirLeaf(_filePaths) + "_" + variable; + // TODO: stop thread exactly until dictionary exists + std::this_thread::sleep_for(std::chrono::milliseconds(5000)); + std::string itemName = _currentVolumeConversionDictionary.value(KeyItemName); std::string itemPathBase = "${DATA}/.internal/volumes_from_cdf/" + itemName; Directory d(itemPathBase, RawPath::No); FileSys.createDirectory(d); - std::string translationType = _currentVolumeConversionDictionary.value(KeyType); - - // Check if user selected a Translation of type Static or Spice - // and create Translation Dictionary ghoul::Dictionary translationDictionary; - if(translationType == KeyStaticTranslation) { - glm::dvec3 position = _currentVolumeConversionDictionary.value(KeyPosition); - - translationDictionary.setValue(KeyPosition, position); - translationDictionary.setValue(KeyType, translationType); - - } else { - std::string target = _currentVolumeConversionDictionary.value(KeyTarget); - std::string observer = _currentVolumeConversionDictionary.value(KeyObserver); - - translationDictionary.setValue(KeyType, translationType); - translationDictionary.setValue(KeyTarget, target); - translationDictionary.setValue(KeyObserver, observer); + if (!_currentVolumeConversionDictionary.getValue(KeyTranslation, translationDictionary)) { + throw ghoul::RuntimeError("Must provide Translation table for volume conversion."); } + // Quick fix to make formatter handle nestled dvec3 type correctly + glm::dvec3 position = translationDictionary.value(KeyPosition); + translationDictionary.setValue(KeyPosition, position); + // Read and create Scale Dictionary float scale = _currentVolumeConversionDictionary.value(KeyScale) * sunRadiusScale; ghoul::Dictionary scaleDictionary; @@ -348,8 +339,6 @@ void Loader::processCurrentlySelectedUploadData(const std::string& dictionaryStr transformDictionary.setValue(KeyTranslation, translationDictionary); transformDictionary.setValue(KeyScale, scaleDictionary); - std::string gridType = _currentVolumeConversionDictionary.value(KeyGridType); - /*** create state file ***/ // Check if file exists // If it exists, clear contents? delete and create new? @@ -374,7 +363,8 @@ void Loader::processCurrentlySelectedUploadData(const std::string& dictionaryStr stateStream.close(); /*** copy over tf file ***/ - std::ifstream tfSource(absPath("${DATA}/assets/scene/solarsystem/model/mas/transferfunctions/mas_mhd_r_squared.txt")); + const std::string tfFileToCopyPath = _currentVolumeConversionDictionary.value(KeyTransferFunctionPath); + std::ifstream tfSource(tfFileToCopyPath); std::ofstream tfDest(absPath(itemPathBase + "/transferfunction.txt")); if (!tfSource) { LERROR("Could not open source transferfunction file."); diff --git a/modules/dataloader/operators/reader.cpp b/modules/dataloader/operators/reader.cpp index bfc62d350a..32cc5162a6 100644 --- a/modules/dataloader/operators/reader.cpp +++ b/modules/dataloader/operators/reader.cpp @@ -25,17 +25,19 @@ #include #include +#include #include #include #include #include #include +#include +#include +#include #include #include - #include -#include #ifdef _WIN32 #include @@ -44,7 +46,9 @@ using Directory = ghoul::filesystem::Directory; using Recursive = ghoul::filesystem::Directory::Recursive; +using RawPath = ghoul::filesystem::Directory::RawPath; using Sort = ghoul::filesystem::Directory::Sort; +using json = nlohmann::json; namespace { constexpr const char* _loggerCat = "Reader"; @@ -70,10 +74,7 @@ Reader::Reader() : PropertyOwner({ "Reader" }) // , _readVolumesTrigger(ReadVolumesTriggerInfo) { - _topDir = ghoul::filesystem::Directory( - "${DATA}/.internal", - ghoul::filesystem::Directory::RawPath::No - ); + _topDir = ghoul::filesystem::Directory("${DATA}/.internal", RawPath::No); // _readVolumesTrigger.onChange([this](){ // readVolumeDataItems(); @@ -114,6 +115,34 @@ void Reader::readVolumeDataItems() { // Store a reference somehow if necessary } +std::string Reader::readTransferFunctionPresets() { + Directory d(_topDir.path() + ghoul::filesystem::FileSystem::PathSeparator + "tf_presets", RawPath::Yes); + + std::vector tfFiles = d.readFiles(Recursive::No, Sort::No); + std::vector> tfLinkList; + + // Put last line of transferfunction preset files together with file path in tfLinkList + for (auto file : tfFiles) { + std::ifstream in; + in.open(file); + if (in.is_open()) { + std::vector lines; + copy(std::istream_iterator(in), + std::istream_iterator(), + back_inserter(lines)); + tfLinkList.push_back(std::make_pair(file, lines.back())); + } + in.close(); + } + + auto j = json::array(); + for (auto pair : tfLinkList) { + j.push_back(json::object({ {"path", pair.first}, {"image", pair.second} })); + } + + return j.dump(); +} + Directory Reader::getVolumeDir() { return Directory( _topDir.path() + diff --git a/modules/dataloader/operators/reader.h b/modules/dataloader/operators/reader.h index 33e12bdc27..07f2c4916a 100644 --- a/modules/dataloader/operators/reader.h +++ b/modules/dataloader/operators/reader.h @@ -42,6 +42,7 @@ class Reader : public PropertyOwner, public Operator { Directory getVolumeDir(); void readVolumeDataItems(); + std::string readTransferFunctionPresets(); private: ghoul::filesystem::Directory _topDir; diff --git a/modules/webgui/web/src/components/DataLoader/PrepareUploadedData.jsx b/modules/webgui/web/src/components/DataLoader/PrepareUploadedData.jsx index dbcc393bd5..354dd56547 100644 --- a/modules/webgui/web/src/components/DataLoader/PrepareUploadedData.jsx +++ b/modules/webgui/web/src/components/DataLoader/PrepareUploadedData.jsx @@ -40,6 +40,11 @@ class PrepareUploadedData extends Component { uploadButtonIsClicked: false, itemName: '', gridType: '', + tfLinkList: [{ + image: '', + path: '' + }], + activeTfFilePath: '', translationType: KEY_STATIC_TRANSLATION, translationPos: { x: 0, y: 0, z: 0 }, translationTarget: 'SUN', @@ -55,6 +60,7 @@ class PrepareUploadedData extends Component { } }; + this.onChangeMultiInputs = this.onChangeMultiInputs.bind(this); this.changeVariable = this.changeVariable.bind(this); this.changeRSquared = this.changeRSquared.bind(this); @@ -68,6 +74,7 @@ class PrepareUploadedData extends Component { this.handleSetStaticTranslation = this.handleSetStaticTranslation.bind(this); this.handleSetTranslationTarget = this.handleSetTranslationTarget.bind(this); this.handleSelectedTFImage = this.handleSelectedTFImage.bind(this); + this.handleTfPresetsJSON = this.handleTfPresetsJSON.bind(this); } componentDidUpdate(prevProps, prevState) { @@ -83,14 +90,28 @@ class PrepareUploadedData extends Component { this.subscribeToVolumeConversionProgress(); } - handleProgressValue(data) { - this.setState({volumeProgress: data.Value}); + componentDidMount() { + DataManager.trigger('Modules.DataLoader.ReadTransferFunctionPresets'); + DataManager.subscribe('Modules.DataLoader.TransferFunctionPresetsJSON', this.handleTfPresetsJSON); + } + + handleTfPresetsJSON(jsonData) { + let json = jsonData.Value; + json = json.replace(/\\"/g, '"'); + const parsedJson = JSON.parse(json); + console.log(parsedJson) + + this.setState({tfLinkList: JSON.parse(json)}); } subscribeToVolumeConversionProgress() { DataManager.subscribe('Modules.DataLoader.Loader.VolumeConversionProgress', this.handleProgressValue); } + handleProgressValue(data) { + this.setState({volumeProgress: data.Value}); + } + onChangeMultiInputs({ currentTarget }, dataToChange) { const keyToChange = currentTarget.attributes.label.nodeValue; const valueToSet = Number(currentTarget.value); @@ -153,54 +174,58 @@ class PrepareUploadedData extends Component { } handleSelectedTFImage(imgSource) { - const filename = imgSource.substring(imgSource.lastIndexOf('/')+1); - filename.replace(/\.[^/.]+$/, ''); - console.log(filename) + let activeTfFilePath = ''; + this.state.tfLinkList.map(pair => { + console.log(pair) + if (pair.image === imgSource) { + activeTfFilePath = pair.path; + } + }) + + this.setState({activeTfFilePath}); } upload() { this.setState({uploadButtonIsClicked: true}); - const { translationType, translationPos, translationTarget, translationObserver } = this.state; + const { translationType, translationPos, translationTarget, translationObserver, activeTfFilePath } = this.state; const { dimensions, variable, lowerDomainBounds, upperDomainBounds, rSquared } = this.state.data; - let transform = ` - Type = "${translationType}", - ${translationType === KEY_STATIC_TRANSLATION ? ` - Position={${translationPos.x}, ${translationPos.y}, ${translationPos.z}}, - ` : ` - Target = "${translationTarget}", - Observer = "${translationObserver}" - `} - }` - let payload = `\' return { - ItemName = "${this.state.itemName || this.getDefaultItemName()}", - GridType = "${this.state.gridType}", - ${transform}, - Task = { + ItemName="${this.state.itemName || this.getDefaultItemName()}", + GridType="${this.state.gridType}", + Translation={ + Type="${translationType}", + ${translationType === KEY_STATIC_TRANSLATION ? ` + Position={${translationPos.x}, ${translationPos.y}, ${translationPos.z}}, + ` : ` + Target="${translationTarget}", + Observer="${translationObserver}", + `} + }, + Task={ Dimensions={${dimensions.x}, ${dimensions.y}, ${dimensions.z}}, Variable="${variable.toLowerCase()}", LowerDomainBound={${lowerDomainBounds.r}, ${lowerDomainBounds.theta}, ${lowerDomainBounds.phi}}, UpperDomainBound={${upperDomainBounds.r}, ${upperDomainBounds.theta}, ${upperDomainBounds.phi}}, FactorRSquared="${rSquared.toString()}", }, - Scale = ${this.state.scale} + Scale=${this.state.scale}, + TransferFunctionPath="${activeTfFilePath}" } \'` payload = removeLineBreakCharacters(payload); + // let p2 = `\' + // return { + // ItemName="STRING" + // } + // \'` + // p2 = removeLineBreakCharacters(p2); + const payloadScript = UploadDataItemScript.replace(ValuePlaceholder, payload); DataManager.runScript(payloadScript); - - // const transformScript = UploadDataItemScript.replace(ValuePlaceholder, transform); - // DataManager.runScript(transformScript); - - } - - changeVariable(event) { - this.setState({ variable: event.value}); } render() { diff --git a/modules/webgui/web/src/components/DataLoader/components/ImageSelect.jsx b/modules/webgui/web/src/components/DataLoader/components/ImageSelect.jsx index 34094172a8..2fdc28c132 100644 --- a/modules/webgui/web/src/components/DataLoader/components/ImageSelect.jsx +++ b/modules/webgui/web/src/components/DataLoader/components/ImageSelect.jsx @@ -14,6 +14,20 @@ class ImageSelect extends React.Component { } this.handleClick = this.handleClick.bind(this); + this.selectFirst = this.selectFirst.bind(this); + } + + componentDidUpdate() { + const { defaultToFirst, imageSources } = this.props; + + if (defaultToFirst && imageSources.length > 0 && this.state.selectedImgSrc === '') { + this.selectFirst(); + } + } + + selectFirst() { + const firstImageSrc = this.props.imageSources[0].images[0]; + this.setState({selectedImgSrc: firstImageSrc}, this.props.onSelect(firstImageSrc)) } handleClick(event) { @@ -48,17 +62,19 @@ class ImageSelect extends React.Component { } ImageSelect.propTypes = { - selectedImgSrc: PropTypes.string, + defaultToFirst: PropTypes.bool, imageSources: PropTypes.arrayOf(PropTypes.shape({ label: PropTypes.string, images: PropTypes.arrayOf(PropTypes.string) })), - onSelect: PropTypes.func + onSelect: PropTypes.func, + selectedImgSrc: PropTypes.string } ImageSelect.defaultProps = { - selectedImgSrc: '', - imageSources: [] + defaultToFirst: true, + imageSources: [], + selectedImgSrc: '' } export default ImageSelect; \ No newline at end of file