diff --git a/Dockerfile-local b/Dockerfile-local
index be4e450b..5f48aa85 100644
--- a/Dockerfile-local
+++ b/Dockerfile-local
@@ -36,6 +36,6 @@ EXPOSE 5055
# Command to run Gunicorn
#CMD ["poetry", "run", "gunicorn", "-w", "1", "-b", "0.0.0.0:5055", "mdvtools.dbutils.mdv_server_app:app"]
-CMD ["poetry", "run", "python", "-m", "mdvtools.dbutils.mdv_server_app"]
+CMD ["poetry", "run", "python", "-u", "-m", "mdvtools.dbutils.mdv_server_app"]
diff --git a/docker-local.yml b/docker-local.yml
index 184d9698..fa627d5c 100644
--- a/docker-local.yml
+++ b/docker-local.yml
@@ -2,17 +2,23 @@ services:
mdv_app:
build:
context: .
- dockerfile: Dockerfile
+ dockerfile: Dockerfile-local
#image: jayeshire/mdv-frontend:latest
ports:
- "5055:5055"
volumes:
+ - ./python:/app/python
- mdv-data:/app/mdv
- ./secrets/db_user:/run/secrets/db_user:ro
- ./secrets/db_password:/run/secrets/db_password:ro
- ./secrets/db_name:/run/secrets/db_name:ro
+ environment:
+ - FLASK_ENV=development
+ - FLASK_DEBUG=1
+ - PYTHONUNBUFFERED=1
depends_on:
- mdv_db
+ restart: always
mdv_db:
@@ -26,6 +32,7 @@ services:
POSTGRES_USER_FILE: /run/secrets/db_user
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
POSTGRES_DB_FILE: /run/secrets/db_name
+ restart: always
volumes:
diff --git a/package.json b/package.json
index b9bd420b..f8c6c08e 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,8 @@
"python-setup": "python -m venv venv && source venv/bin/activate && cd python && poetry install --with dev && npm run build-flask-vite",
"mdv_desktop": "source venv/bin/activate && python -m mdvtools.mdv_desktop",
"docker_build": "docker-compose build && docker-compose -f docker-compose-dev.yml up -d --force-recreate",
- "docker_dev": "docker-compose -f docker-compose-dev.yml up -d --force-recreate",
+ "docker_dev": "docker-compose -f docker-local.yml up -d --force-recreate",
+ "docker_purge": "docker rm -f $(docker ps -a -q) && docker rmi -f $(docker images -q) && docker volume rm $(docker volume ls -q)",
"docker_stack_rm": "docker stack rm mdv_stack",
"docker_createandpush": "docker build -t my_mdv_app_image:latest . && docker tag my_mdv_app_image:latest jayeshire/my_mdv_app_image:latest && docker push jayeshire/my_mdv_app_image:latest",
"docker_stack_deploy": "docker stack deploy -c docker-compose-stack.yml mdv_stack",
diff --git a/python/mdvtools/dbutils/test/__init__.py b/python/mdvtools/dbutils/test/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/python/mdvtools/dbutils/test/integration/__init__.py b/python/mdvtools/dbutils/test/integration/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/python/mdvtools/dbutils/test/unit/__init__.py b/python/mdvtools/dbutils/test/unit/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/python/mdvtools/dbutils/test/unit/test_dbservice.py b/python/mdvtools/dbutils/test/unit/test_dbservice.py
index b94b7478..f09e34c9 100644
--- a/python/mdvtools/dbutils/test/unit/test_dbservice.py
+++ b/python/mdvtools/dbutils/test/unit/test_dbservice.py
@@ -1,12 +1,12 @@
import unittest
from unittest.mock import patch, MagicMock
-from project_service import ProjectService, FileService
+from mdvtools.dbutils.dbservice import ProjectService, FileService
from mdvtools.dbutils.dbmodels import Project, File
from datetime import datetime
class TestProjectService(unittest.TestCase):
- @patch('project_service.Project.query')
+ @patch('mdvtools.dbutils.dbmodels.Project.query')
def test_get_active_projects_success(self, mock_query):
mock_query.filter_by.return_value.all.return_value = [MagicMock(), MagicMock()]
result = ProjectService.get_active_projects()
diff --git a/python/mdvtools/mdvproject.py b/python/mdvtools/mdvproject.py
index c2e87e84..d0fd4e57 100644
--- a/python/mdvtools/mdvproject.py
+++ b/python/mdvtools/mdvproject.py
@@ -20,6 +20,7 @@
from .charts.view import View
import time
import copy
+from mdvtools.dbutils.dbservice import ProjectService, FileService
DataSourceName = str # NewType("DataSourceName", str)
ColumnName = str # NewType("ColumnName", str)
@@ -259,7 +260,7 @@ def add_images_to_datasource(
print(f"Added image set {name} to {ds} datasource")
self.set_datasource_metadata(ds_metadata)
- def add_or_update_image_datasource(self, tiff_metadata, datasource_name):
+ def add_or_update_image_datasource(self, tiff_metadata, datasource_name, file, project_id):
"""Add or update an image datasource in datasources.json"""
try:
# Load current datasources
@@ -272,36 +273,92 @@ def add_or_update_image_datasource(self, tiff_metadata, datasource_name):
# Update the existing datasource and check the result
update_success = self.update_datasource(datasource, tiff_metadata)
if not update_success:
+ error_message = "update_datasource failed"
print(f"Failed to update datasource '{datasource_name}'.")
return False
else:
# Create a new datasource
# Uncomment and implement the following line if needed
# creation_success = self.create_new_datasource(tiff_metadata, datasource_name)
- print(f"Datasource '{datasource_name}' does not exist and creation is not implemented yet.")
+ print(f"Datasource '{datasource_name}' does not exist")
+ return False
+
+ # Upload the TIFF file only if the datasource update was successful
+ upload_success = self.upload_image_file(file, project_id)
+ if not upload_success:
+ print(f"Failed to upload TIFF file for datasource '{datasource_name}'.")
return False
+ # If both update and upload succeed, return True
+ #file_path = ''
+ #db_update_success = FileService.add_or_update_file_in_project(
+ # file_name=file.filename,
+ # file_path=file.filename, # Assuming 'file_path' is the filename in this example, adjust as necessary
+ # project_id=project_id
+ #)
return True
except Exception as e:
print(f"Error updating or adding datasource '{datasource_name}': {e}")
return False
+
+ def upload_image_file(self, file, project_id):
+ """Upload the TIFF file to the imagefolder, saving it with the original filename."""
+ try:
+ # Define the target folder inside imagefolder (e.g., /images/avivator)
+ target_folder = os.path.join(self.imagefolder, 'avivator')
+ print(target_folder)
+ # Ensure the target folder exists
+ if not os.path.exists(target_folder):
+ os.makedirs(target_folder)
+
+ # Get the original filename from the file
+ original_filename = file.filename # This will give you the name of the uploaded file
+
+ # Create the full file path inside /images/avivator
+ file_path = os.path.join(target_folder, original_filename)
+
+ # Save the file to the /images/avivator folder
+ file.save(file_path)
+ print(f"File uploaded successfully to {file_path}")
+
+ # Update the database with the file information
+ db_file = FileService.add_or_update_file_in_project(
+ file_name=original_filename,
+ file_path=file_path, # The full path where the file was saved
+ project_id=project_id
+ )
+
+ # Check if the file was successfully added or updated in the database
+ if db_file is None:
+ raise ValueError(f"Failed to add file '{original_filename}' to the database.")
+ else:
+ print(f"Added file to DB: {db_file}")
+
+ return True
+ except Exception as e:
+ print(f"Error uploading file: {e}")
+ return False
def update_datasource(self, datasource, tiff_metadata):
"""Update an existing datasource with new image metadata."""
try:
- # Extract image metadata from tiff_metadata
- width = tiff_metadata['OME']['Image']['Pixels']['SizeX']
- height = tiff_metadata['OME']['Image']['Pixels']['SizeY']
- scale = tiff_metadata['OME']['Image']['Pixels']['PhysicalSizeX']
- scale_unit = tiff_metadata['OME']['Image']['Pixels'].get('PhysicalSizeUnit', 'µm') # Default to µm if not present
-
+ print("*****1")
+
+ # Corrected path to access size and scale information
+ pixels_data = tiff_metadata['Pixels']
+ width = pixels_data['SizeX']
+ height = pixels_data['SizeY']
+ scale = pixels_data['PhysicalSizeX']
+ scale_unit = pixels_data.get('PhysicalSizeXUnit', 'µm') # Default to µm if not present
+
+ print("*****2")
# Ensure datasource has a 'regions' field
if "regions" not in datasource:
datasource["regions"] = {"all_regions": {}}
# Determine region name
- region_name = tiff_metadata.get('name', 'unknown') # Default to 'unknown' if not present in metadata
+ region_name = tiff_metadata.get('Name', 'unknown') # Use 'Name' from metadata or 'unknown'
# Define new region with metadata
new_region = {
@@ -324,25 +381,27 @@ def update_datasource(self, datasource, tiff_metadata):
}
image_metadata = {
- 'path': tiff_metadata['path']
+ 'path': tiff_metadata.get('path', '') # Use the 'path' key if it exists, or default to ''
}
# Update or add the region in the datasource
datasource["regions"]["all_regions"][region_name] = new_region
datasource['size'] = len(datasource['regions']['all_regions'])
+ print("*****3")
# Save the updated datasource
self.set_datasource_metadata(datasource)
-
+ print("*****4")
# Update views and images
- self.add_viv_viewer(region_name, [{'name': 'DAPI'}])
- self.add_viv_images(region_name, image_metadata, link_images=True)
+ #self.add_viv_viewer(region_name, [{'name': 'DAPI'}])
+ #self.add_viv_images(region_name, image_metadata, link_images=True)
print(f"Datasource '{datasource.get('name', 'unknown')}' updated successfully.")
return True
except Exception as e:
print(f"Error updating datasource '{datasource.get('name', 'unknown')}': {e}")
return False
+
diff --git a/python/mdvtools/server.py b/python/mdvtools/server.py
index 18c9efab..31c044c6 100644
--- a/python/mdvtools/server.py
+++ b/python/mdvtools/server.py
@@ -248,24 +248,37 @@ def save_data():
@project_bp.route("/add_or_update_image_datasource", methods=["POST"])
def add_or_update_image_datasource():
try:
- # Extract data from the request
- data = request.json
- if not data:
- return "Request must contain JSON data with tiffMetadata & datasourceName", 400
- tiff_metadata = data.get('tiffMetadata')
- datasource_name = data.get('datasourceName')
-
- if not tiff_metadata or not datasource_name:
- return "Request must contain JSON data with tiffMetadata & datasourceName", 400
+ # Check if request has a file part
+ if 'file' not in request.files:
+ return "No file part in the request", 400
+
+ # Get the file from the request
+ file = request.files['file']
+
+ # Get the text fields from the request form
+ datasource_name = request.form.get('datasourceName') # ""
+ tiff_metadata = request.form.get('tiffMetadata')
+
+ # Validate the presence of required fields
+ if not file or not tiff_metadata:
+ return jsonify({"status": "error", "message": "Missing file, tiffMetadata, or datasourceName"}), 400
+
+ # If tiff_metadata is sent as JSON string, deserialize it
+ try:
+ tiff_metadata = json.loads(tiff_metadata)
+ except Exception as e:
+ return jsonify({"status": "error", "message": f"Invalid JSON format for tiffMetadata: {e}"}), 400
+
+ # Call your method to add or update the image datasource
+ success = project.add_or_update_image_datasource(tiff_metadata, datasource_name, file, project.id)
- # Call the method in the project class to add or update image datasource
- success = project.add_or_update_image_datasource(tiff_metadata, datasource_name)
if success:
- return "Image datasource updated successfully", 200
+ return jsonify({"status": "success", "message": "Image datasource updated and file uploaded successfully"}), 200
else:
return "Failed to update image datasource", 500
except Exception as e:
- return str(e), 500
+ return jsonify({"status": "error", "message": str(e)}), 500
+
@project_bp.route("/add_datasource", methods=["POST"])
def add_datasource():
diff --git a/src/charts/dialogs/FileUploadDialog.tsx b/src/charts/dialogs/FileUploadDialog.tsx
index 2e025534..d974b2d9 100644
--- a/src/charts/dialogs/FileUploadDialog.tsx
+++ b/src/charts/dialogs/FileUploadDialog.tsx
@@ -1,237 +1,237 @@
import type React from "react";
import {
- useState,
- useCallback,
- useReducer,
- type PropsWithChildren,
- forwardRef,
+ useState,
+ useCallback,
+ useReducer,
+ type PropsWithChildren,
+ forwardRef,
} from "react";
import { useDropzone } from "react-dropzone";
-import { observer } from "mobx-react-lite";
+import { observer } from 'mobx-react-lite';
-import axios from "axios";
+import axios from 'axios';
import { useProject } from "../../modules/ProjectContext";
-import { ColumnPreview } from "./ColumnPreview";
+import { ColumnPreview } from "./ColumnPreview"
-import {
- useViewerStoreApi,
- useChannelsStoreApi,
-} from "../../react/components/avivatorish/state";
-import { createLoader } from "../../react/components/avivatorish/utils";
-import { unstable_batchedUpdates } from "react-dom";
+import { useViewerStoreApi, useChannelsStoreApi } from '../../react/components/avivatorish/state';
+import { createLoader } from '../../react/components/avivatorish/utils';
+import { unstable_batchedUpdates } from 'react-dom';
import { TiffPreview } from "./TiffPreview";
-import { TiffMetadataTable } from "./TiffMetadataTable";
-import TiffVisualization from "./TiffVisualization";
+import { TiffMetadataTable } from "./TiffMetadataTable"
+import TiffVisualization from './TiffVisualization';
import { DatasourceDropdown } from "./DatasourceDropdown";
-import CloudUploadIcon from "@mui/icons-material/CloudUpload";
+
// Use dynamic import for the worker
const CsvWorker = new Worker(new URL("./csvWorker.ts", import.meta.url), {
- type: "module",
+ type: "module",
});
const Container = ({ children }: PropsWithChildren) => {
- return (
-
- {children}
-
- );
+ return (
+
+ {children}
+
+ );
};
const StatusContainer = ({ children }: PropsWithChildren) => {
- return (
-
- {children}
-
- );
+ return (
+
+ {children}
+
+ );
};
const SuccessContainer = ({ children }) => (
-
- {children}
-
+
+ {children}
+
);
const SuccessHeading = ({ children }) => (
- {children}
+ {children}
);
const SuccessText = ({ children }) => (
-
- {children}
-
+
+ {children}
+
);
const DropzoneContainer = forwardRef(
- ({ isDragOver, children, ...props }: any, ref) => (
-
- {children}
-
- ),
+ ({ isDragOver, children, ...props }: any, ref) => (
+
+ {children}
+
+ ),
);
const FileInputLabel = ({ children, ...props }) => (
-
+
);
const Spinner = () => {
- return (
-
- );
+ return (
+
+ );
};
const colorStyles = {
- blue: {
- bgColor: "bg-blue-600",
- hoverColor: "hover:bg-blue-700",
- darkBgColor: "dark:bg-blue-800",
- darkHoverColor: "dark:bg-blue-900",
- },
- red: {
- bgColor: "bg-red-600",
- hoverColor: "hover:bg-red-700",
- darkBgColor: "dark:bg-red-800",
- darkHoverColor: "dark:bg-red-900",
- },
- green: {
- bgColor: "bg-green-600",
- hoverColor: "hover:bg-green-700",
- darkBgColor: "dark:bg-green-800",
- darkHoverColor: "dark:bg-green-900",
- },
- gray: {
- bgColor: "bg-gray-600",
- hoverColor: "hover:bg-gray-700",
- darkBgColor: "dark:bg-gray-800",
- darkHoverColor: "dark:bg-gray-900",
- },
+ blue: {
+ bgColor: "bg-blue-600",
+ hoverColor: "hover:bg-blue-700",
+ darkBgColor: "dark:bg-blue-800",
+ darkHoverColor: "dark:bg-blue-900",
+ },
+ red: {
+ bgColor: "bg-red-600",
+ hoverColor: "hover:bg-red-700",
+ darkBgColor: "dark:bg-red-800",
+ darkHoverColor: "dark:bg-red-900",
+ },
+ green: {
+ bgColor: "bg-green-600",
+ hoverColor: "hover:bg-green-700",
+ darkBgColor: "dark:bg-green-800",
+ darkHoverColor: "dark:bg-green-900",
+ },
+ gray: {
+ bgColor: "bg-gray-600",
+ hoverColor: "hover:bg-gray-700",
+ darkBgColor: "dark:bg-gray-800",
+ darkHoverColor: "dark:bg-gray-900",
+ },
};
const Button = ({
- onClick,
- color = "blue",
- disabled = false,
- size = "px-5 py-2.5",
- marginTop = "mt-2.5",
- children,
+ onClick,
+ color = "blue",
+ disabled = false,
+ size = "px-5 py-2.5",
+ marginTop = "mt-2.5",
+ children,
}) => {
- const { bgColor, hoverColor, darkBgColor, darkHoverColor } =
- colorStyles[color] || colorStyles.blue;
-
- return (
-
- );
+ const { bgColor, hoverColor, darkBgColor, darkHoverColor } =
+ colorStyles[color] || colorStyles.blue;
+
+ return (
+
+ );
};
const ProgressBar = ({ value, max }) => (
-
+
);
const Message = ({ children }) => (
-
- {children}
-
+
+ {children}
+
);
const FileSummary = ({ children }) => (
- {children}
+ {children}
);
const FileSummaryHeading = ({ children }) => (
- {children}
+ {children}
);
const FileSummaryText = ({ children }) => (
- {children}
+ {children}
);
const ErrorContainer = ({ children }) => (
-
- {children}
-
+
+ {children}
+
);
const DynamicText = ({ text, className = "" }) => (
-
+
);
const ErrorHeading = ({ children }) => (
- {children}
+ {children}
);
const DatasourceNameInput = ({ value, onChange, isDisabled }) => (
-
-
-
-
+
+
+
+
);
// Reducer function
const reducer = (state, action) => {
- switch (action.type) {
- case "SET_SELECTED_FILES":
- return { ...state, selectedFiles: action.payload };
- case "SET_IS_UPLOADING":
- return { ...state, isUploading: action.payload };
- case "SET_IS_INSERTING":
- return { ...state, isInserting: action.payload };
- case "SET_SUCCESS":
- return { ...state, success: action.payload };
- case "SET_ERROR":
- return { ...state, error: action.payload };
- case "SET_IS_VALIDATING":
- return { ...state, isValidating: action.payload };
- case "SET_VALIDATION_RESULT":
- return { ...state, validationResult: action.payload };
- case "SET_FILE_TYPE":
- return { ...state, fileType: action.payload };
- case "SET_TIFF_METADATA":
- return { ...state, tiffMetadata: action.payload };
- default:
- return state;
- }
+ switch (action.type) {
+ case "SET_SELECTED_FILES":
+ return { ...state, selectedFiles: action.payload };
+ case "SET_IS_UPLOADING":
+ return { ...state, isUploading: action.payload };
+ case "SET_IS_INSERTING":
+ return { ...state, isInserting: action.payload };
+ case "SET_SUCCESS":
+ return { ...state, success: action.payload };
+ case "SET_ERROR":
+ return { ...state, error: action.payload };
+ case "SET_IS_VALIDATING":
+ return { ...state, isValidating: action.payload };
+ case "SET_VALIDATION_RESULT":
+ return { ...state, validationResult: action.payload };
+ case "SET_FILE_TYPE":
+ return { ...state, fileType: action.payload };
+ case "SET_TIFF_METADATA":
+ return { ...state, tiffMetadata: action.payload };
+ default:
+ return state;
+ }
};
// Constants
@@ -240,698 +240,563 @@ const UPLOAD_DURATION = 3000;
const UPLOAD_STEP = 100 / (UPLOAD_DURATION / UPLOAD_INTERVAL);
interface FileUploadDialogComponentProps {
- onClose: () => void;
- onResize: (width: number, height: number) => void; // Add this prop
+ onClose: () => void;
+ onResize: (width: number, height: number) => void; // Add this prop
}
// Custom hook for file upload progress
const useFileUploadProgress = () => {
- const [progress, setProgress] = useState(0);
-
- const startProgress = useCallback(() => {
- setProgress(0);
- const interval = setInterval(() => {
- setProgress((prevProgress) => {
- const updatedProgress = prevProgress + UPLOAD_STEP;
- if (updatedProgress >= 100) {
- clearInterval(interval);
- return 100;
- }
- return updatedProgress;
- });
- }, UPLOAD_INTERVAL);
- }, []);
+ const [progress, setProgress] = useState(0);
+
+ const startProgress = useCallback(() => {
+ setProgress(0);
+ const interval = setInterval(() => {
+ setProgress((prevProgress) => {
+ const updatedProgress = prevProgress + UPLOAD_STEP;
+ if (updatedProgress >= 100) {
+ clearInterval(interval);
+ return 100;
+ }
+ return updatedProgress;
+ });
+ }, UPLOAD_INTERVAL);
+ }, []);
- const resetProgress = useCallback(() => {
- setProgress(0);
- }, []);
+ const resetProgress = useCallback(() => {
+ setProgress(0);
+ }, []);
- return { progress, setProgress, startProgress, resetProgress };
+ return { progress, setProgress, startProgress, resetProgress };
};
const FileUploadDialogComponent: React.FC = ({
- onClose,
- onResize,
+ onClose,
+ onResize,
}) => {
- const { root, projectName, chartManager } = useProject();
- const [selectedOption, setSelectedOption] = useState(null);
- const [updatedNamesArray, setUpdatedNamesArray] = useState([]);
+ //const viewerStore = vivStores.viewerStore;
+
+ const { root, projectName, chartManager } = useProject();
+
+ const [selectedOption, setSelectedOption] = useState(null);
+
+ const [updatedNamesArray, setUpdatedNamesArray] = useState([]);
+
+ const handleSelect = (value: string) => {
+ setSelectedOption(value);
+ setDatasourceName(value);
+ console.log('Selected:', value);
+ };
+
+ const [datasourceName, setDatasourceName] = useState("");
+
+ const [showMetadata, setShowMetadata] = useState(true);
+
+ const toggleView = () => {
+ setShowMetadata(prevState => !prevState);
+ };
+
+ const [csvSummary, setCsvSummary] = useState({
+ datasourceName: "",
+ fileName: "",
+ fileSize: "",
+ rowCount: 0,
+ columnCount: 0,
+ });
+
+ const [tiffSummary, settiffSummary] = useState({
+ fileName: "",
+ fileSize: "",
+ });
+
+ const [columnNames, setColumnNames] = useState([]);
+ const [columnTypes, setColumnTypes] = useState([]);
+ const [secondRowValues, setSecondRowValues] = useState([]);
+
+ // TIFF
+ const channelsStore = useChannelsStoreApi();
+
+ const [state, dispatch] = useReducer(reducer, {
+ selectedFiles: [],
+ isUploading: false,
+ isInserting: false,
+ isValidating: false,
+ validationResult: null,
+ success: false,
+ error: null,
+ fileType: null,
+ tiffMetadata: null,
+ });
+
+ const viewerStore = useViewerStoreApi();
+
+ const { progress, resetProgress, setProgress } = useFileUploadProgress();
+
+ const onDrop = useCallback((acceptedFiles: File[]) => {
+ dispatch({ type: "SET_SELECTED_FILES", payload: acceptedFiles });
+ if (acceptedFiles.length > 0) {
+ const file = acceptedFiles[0];
+ const fileExtension = file.name.split('.').pop().toLowerCase();
+ if (fileExtension === 'csv') {
+ const newDatasourceName = file.name;
+ setDatasourceName(newDatasourceName);
+ setCsvSummary({
+ ...csvSummary,
+ datasourceName: newDatasourceName,
+ fileName: file.name,
+ fileSize: (file.size / (1024 * 1024)).toFixed(2)
+ });
+ dispatch({ type: "SET_FILE_TYPE", payload: "csv" });
+ } else if (fileExtension === 'tiff' || fileExtension === 'tif') {
+
+ const dataSources = window.mdv.chartManager?.dataSources ?? [];
+ const namesArray = dataSources.map(dataSource => dataSource.name);
+
+ // Update state with the new array, triggering re-render
+ setUpdatedNamesArray([...namesArray, "new datasource"]);
+ settiffSummary({
+ ...tiffSummary,
+ fileName: file.name,
+ fileSize: (file.size / (1024 * 1024)).toFixed(2)
+ });
- const handleSelect = (value: string) => {
- setSelectedOption(value);
- setDatasourceName(value);
- console.log("Selected:", value);
- };
+ dispatch({ type: "SET_FILE_TYPE", payload: "tiff" });
+ handleSubmitFile(acceptedFiles);
+ }
+ }
+ }, [csvSummary]);
+
+ const handleSubmitFile = useCallback(async (files: File[]) => {
+ let newSource;
+ if (files.length === 1) {
+ newSource = {
+ urlOrFile: files[0],
+ description: files[0].name
+ };
+ } else {
+ newSource = {
+ urlOrFile: files,
+ description: 'data.zarr'
+ };
+ }
- const [datasourceName, setDatasourceName] = useState("");
+ viewerStore.setState({ isChannelLoading: [true] });
+ viewerStore.setState({ isViewerLoading: true });
+
+ try {
+ const newLoader = await createLoader(
+ newSource.urlOrFile,
+ () => { }, // placeholder for toggleIsOffsetsSnackbarOn
+ (message) => viewerStore.setState({ loaderErrorSnackbar: { on: true, message } })
+ );
+
+ let nextMeta;
+ let nextLoader;
+ if (Array.isArray(newLoader)) {
+ if (newLoader.length > 1) {
+ nextMeta = newLoader.map(l => l.metadata);
+ nextLoader = newLoader.map(l => l.data);
+ } else {
+ nextMeta = newLoader[0].metadata;
+ nextLoader = newLoader[0].data;
+ }
+ } else {
+ nextMeta = newLoader.metadata;
+ nextLoader = newLoader.data;
+ }
- const [showMetadata, setShowMetadata] = useState(true);
+ if (nextLoader) {
+ console.log('Metadata (in JSON-like form) for current file being viewed: ', nextMeta);
- const toggleView = () => {
- setShowMetadata((prevState) => !prevState);
- };
+ unstable_batchedUpdates(() => {
+ channelsStore.setState({ loader: nextLoader });
+ viewerStore.setState({ metadata: nextMeta });
+ });
- const [csvSummary, setCsvSummary] = useState({
- datasourceName: "",
- fileName: "",
- fileSize: "",
- rowCount: 0,
- columnCount: 0,
- });
-
- const [tiffSummary, settiffSummary] = useState({
- fileName: "",
- fileSize: "",
- });
-
- const [columnNames, setColumnNames] = useState([]);
- const [columnTypes, setColumnTypes] = useState([]);
- const [secondRowValues, setSecondRowValues] = useState([]);
-
- // TIFF
- const channelsStore = useChannelsStoreApi();
-
- const [state, dispatch] = useReducer(reducer, {
- selectedFiles: [],
- isUploading: false,
- isInserting: false,
- isValidating: false,
- validationResult: null,
- success: false,
- error: null,
- fileType: null,
- tiffMetadata: null,
- });
-
- const viewerStore = useViewerStoreApi();
-
- const { progress, resetProgress, setProgress } = useFileUploadProgress();
-
- const onDrop = useCallback(
- (acceptedFiles: File[]) => {
- if (acceptedFiles.length > 0) {
- const file = acceptedFiles[0];
- dispatch({
- type: "SET_SELECTED_FILES",
- payload: acceptedFiles,
- });
- const fileExtension = file.name.split(".").pop().toLowerCase();
-
- // Common logic for both CSV and TIFF files
- if (fileExtension === "csv") {
- const newDatasourceName = file.name;
- setDatasourceName(newDatasourceName);
- setCsvSummary({
- ...csvSummary,
- datasourceName: newDatasourceName,
- fileName: file.name,
- fileSize: (file.size / (1024 * 1024)).toFixed(2),
- });
- dispatch({ type: "SET_FILE_TYPE", payload: "csv" });
- } else if (
- fileExtension === "tiff" ||
- fileExtension === "tif"
- ) {
- const dataSources =
- window.mdv.chartManager?.dataSources ?? [];
- const namesArray = dataSources.map(
- (dataSource) => dataSource.name,
- );
-
- // Update state with the new array, triggering re-render
- setUpdatedNamesArray([...namesArray, "new datasource"]);
- settiffSummary({
- ...tiffSummary,
- fileName: file.name,
- fileSize: (file.size / (1024 * 1024)).toFixed(2),
- });
- dispatch({ type: "SET_FILE_TYPE", payload: "tiff" });
- handleSubmitFile(acceptedFiles);
- }
-
- // Start validation after dispatching file type
- dispatch({ type: "SET_IS_VALIDATING", payload: true });
-
- if (fileExtension === "csv") {
- CsvWorker.postMessage(file);
- CsvWorker.onmessage = (event: MessageEvent) => {
- const {
- columnNames,
- columnTypes,
- secondRowValues,
- rowCount,
- columnCount,
- error,
- } = event.data;
- if (error) {
- dispatch({
- type: "SET_ERROR",
- payload: {
- message: "Validation failed.",
- traceback: error,
- },
- });
- dispatch({
- type: "SET_IS_VALIDATING",
- payload: false,
- });
- } else {
- setColumnNames(columnNames);
- setColumnTypes(columnTypes);
- setSecondRowValues(secondRowValues);
- setCsvSummary((prevCsvSummary) => ({
- ...prevCsvSummary,
- rowCount,
- columnCount,
- }));
-
- const totalWidth = calculateTotalWidth(
- columnNames,
- columnTypes,
- secondRowValues,
- );
- onResize(totalWidth, 745);
- dispatch({
- type: "SET_IS_VALIDATING",
- payload: false,
- });
- dispatch({
- type: "SET_VALIDATION_RESULT",
- payload: { columnNames, columnTypes },
- });
- }
- };
- } else if (fileExtension === "tiff") {
- onResize(1032, 580);
- dispatch({ type: "SET_IS_VALIDATING", payload: false });
- dispatch({
- type: "SET_VALIDATION_RESULT",
- payload: { columnNames, columnTypes },
- });
- }
- }
- },
- [csvSummary, tiffSummary],
+ dispatch({ type: "SET_TIFF_METADATA", payload: nextMeta });
+ }
+ } catch (error) {
+ console.error('Error loading file:', error);
+ dispatch({
+ type: "SET_ERROR",
+ payload: {
+ message: "Error loading TIFF file.",
+ traceback: error.message
+ }
+ });
+ } finally {
+ viewerStore.setState({ isChannelLoading: [false] });
+ viewerStore.setState({ isViewerLoading: false });
+ }
+ }, [viewerStore, channelsStore]);
+
+ const handleDatasourceNameChange = (event) => {
+ const { value } = event.target;
+ setDatasourceName(value); // Update the state with the new value
+ setCsvSummary((prevCsvSummary) => ({
+ ...prevCsvSummary,
+ datasourceName: value, // Update datasourceName in the summary object
+ }));
+ };
+
+ const { getRootProps, getInputProps, isDragActive, fileRejections } = useDropzone({
+ onDrop,
+ accept: {
+ 'text/csv': ['.csv'],
+ 'image/tiff': ['.tiff', '.tif']
+ }
+ });
+ const rejectionMessage = fileRejections.length > 0
+ ? "Only CSV and TIFF files can be selected"
+ : "Drag and drop files here or click the button below to upload";
+
+ const rejectionMessageStyle = fileRejections.length > 0 ? "text-red-500" : "";
+
+ const getTextWidth = (
+ canvas: HTMLCanvasElement,
+ context: CanvasRenderingContext2D,
+ text: string,
+ ) => {
+ context.font = getComputedStyle(document.body).fontSize + " Arial";
+ return context.measureText(text).width;
+ };
+
+ // Function to calculate the maximum total width needed for the ColumnPreview component
+ const calculateTotalWidth = (
+ columnNames: string[],
+ columnTypes: string[],
+ secondRowValues: string[],
+ ) => {
+ const canvas = document.createElement("canvas");
+ const context = canvas.getContext("2d");
+
+ let maxColumnNameWidth = 0;
+ let maxColumnTypeWidth = 0;
+ let maxColumnSecondRowWidth = 0;
+
+ // Calculate the maximum width of column names
+ maxColumnNameWidth = Math.max(
+ ...columnNames.map((name) => getTextWidth(canvas, context, name)),
);
- const handleSubmitFile = useCallback(
- async (files: File[]) => {
- let newSource;
- if (files.length === 1) {
- newSource = {
- urlOrFile: files[0],
- description: files[0].name,
- };
- } else {
- newSource = {
- urlOrFile: files,
- description: "data.zarr",
- };
- }
-
- viewerStore.setState({ isChannelLoading: [true] });
- viewerStore.setState({ isViewerLoading: true });
-
- try {
- const newLoader = await createLoader(
- newSource.urlOrFile,
- () => {}, // placeholder for toggleIsOffsetsSnackbarOn
- (message) =>
- viewerStore.setState({
- loaderErrorSnackbar: { on: true, message },
- }),
- );
-
- let nextMeta;
- let nextLoader;
- if (Array.isArray(newLoader)) {
- if (newLoader.length > 1) {
- nextMeta = newLoader.map((l) => l.metadata);
- nextLoader = newLoader.map((l) => l.data);
- } else {
- nextMeta = newLoader[0].metadata;
- nextLoader = newLoader[0].data;
- }
- } else {
- nextMeta = newLoader.metadata;
- nextLoader = newLoader.data;
- }
-
- if (nextLoader) {
- console.log(
- "Metadata (in JSON-like form) for current file being viewed: ",
- nextMeta,
- );
-
- unstable_batchedUpdates(() => {
- channelsStore.setState({ loader: nextLoader });
- viewerStore.setState({ metadata: nextMeta });
- });
-
- dispatch({ type: "SET_TIFF_METADATA", payload: nextMeta });
- }
- } catch (error) {
- console.error("Error loading file:", error);
- dispatch({
- type: "SET_ERROR",
- payload: {
- message: "Error loading TIFF file.",
- traceback: error.message,
- },
- });
- } finally {
- viewerStore.setState({ isChannelLoading: [false] });
- viewerStore.setState({ isViewerLoading: false });
- }
- },
- [viewerStore, channelsStore],
+ // Calculate the maximum width of column types
+ maxColumnTypeWidth = Math.max(
+ ...columnTypes.map((type) => getTextWidth(canvas, context, type)),
);
- const handleDatasourceNameChange = (event) => {
- const { value } = event.target;
- setDatasourceName(value); // Update the state with the new value
- setCsvSummary((prevCsvSummary) => ({
- ...prevCsvSummary,
- datasourceName: value, // Update datasourceName in the summary object
- }));
- };
-
- const { getRootProps, getInputProps, isDragActive, fileRejections } =
- useDropzone({
- onDrop,
- accept: {
- "text/csv": [".csv"],
- "image/tiff": [".tiff", ".tif"],
- },
- });
- const rejectionMessage =
- fileRejections.length > 0
- ? "Only CSV and TIFF files can be selected"
- : "Drag and drop files here or click the button below to upload";
-
- const rejectionMessageStyle =
- fileRejections.length > 0 ? "text-red-500" : "";
-
- const getTextWidth = (
- canvas: HTMLCanvasElement,
- context: CanvasRenderingContext2D,
- text: string,
- ) => {
- context.font = getComputedStyle(document.body).fontSize + " Arial";
- return context.measureText(text).width;
- };
+ // Calculate the maximum width of second row values
+ maxColumnSecondRowWidth = Math.max(
+ ...secondRowValues.map((value) =>
+ getTextWidth(canvas, context, value),
+ ),
+ );
- // Function to calculate the maximum total width needed for the ColumnPreview component
- const calculateTotalWidth = (
- columnNames: string[],
- columnTypes: string[],
- secondRowValues: string[],
- ) => {
- const canvas = document.createElement("canvas");
- const context = canvas.getContext("2d");
-
- let maxColumnNameWidth = 0;
- let maxColumnTypeWidth = 0;
- let maxColumnSecondRowWidth = 0;
-
- // Calculate the maximum width of column names
- maxColumnNameWidth = Math.max(
- ...columnNames.map((name) => getTextWidth(canvas, context, name)),
- );
-
- // Calculate the maximum width of column types
- maxColumnTypeWidth = Math.max(
- ...columnTypes.map((type) => getTextWidth(canvas, context, type)),
- );
-
- // Calculate the maximum width of second row values
- maxColumnSecondRowWidth = Math.max(
- ...secondRowValues.map((value) =>
- getTextWidth(canvas, context, value),
- ),
- );
-
- // Calculate the total width needed for the ColumnPreview component
- const totalWidth =
- maxColumnNameWidth +
- maxColumnTypeWidth +
- maxColumnSecondRowWidth +
- 32; // Add padding
- canvas.remove();
- return Math.max(800, totalWidth);
- };
+ // Calculate the total width needed for the ColumnPreview component
+ const totalWidth = maxColumnNameWidth + maxColumnTypeWidth + maxColumnSecondRowWidth + 32; // Add padding
+ canvas.remove();
+ return Math.max(500, totalWidth);
+ };
+
+ const handleValidateClick = () => {
+ const file = state.selectedFiles[0];
+ if (file) {
+ dispatch({ type: "SET_IS_VALIDATING", payload: true });
+
+ const fileExtension = file.name.split('.').pop().toLowerCase();
+ if (fileExtension === 'csv') {
+ CsvWorker.postMessage(file);
+ CsvWorker.onmessage = (event: MessageEvent) => {
+ const {
+ columnNames,
+ columnTypes,
+ secondRowValues,
+ rowCount,
+ columnCount,
+ error,
+ } = event.data;
+ if (error) {
+ dispatch({
+ type: "SET_ERROR",
+ payload: {
+ message: "Validation failed.",
+ traceback: error,
+ },
+ });
+ dispatch({ type: "SET_IS_VALIDATING", payload: false });
+ } else {
+ setColumnNames(columnNames);
+ setColumnTypes(columnTypes);
+ setSecondRowValues(secondRowValues);
+ setCsvSummary((prevCsvSummary) => ({
+ ...prevCsvSummary,
+ rowCount,
+ columnCount,
+ }));
+
+ // Calculate the total width needed for the ColumnPreview component
+ const totalWidth = calculateTotalWidth(
+ columnNames,
+ columnTypes,
+ secondRowValues,
+ );
+ onResize(totalWidth, 730);
+ dispatch({ type: "SET_IS_VALIDATING", payload: false });
+ dispatch({
+ type: "SET_VALIDATION_RESULT",
+ payload: { columnNames, columnTypes },
+ });
+ }
+ };
+ } else if (fileExtension === 'tiff') {
+ onResize(1032, 580);
+ dispatch({ type: "SET_IS_VALIDATING", payload: false });
+ dispatch({ type: "SET_VALIDATION_RESULT", payload: { columnNames, columnTypes } });
+ }
+ }
+ };
- const handleUploadClick = async () => {
- console.log("Uploading file...");
+ const handleUploadClick = async () => {
+ console.log("Uploading file...");
+ if (!state.selectedFiles.length) {
+ dispatch({ type: "SET_ERROR", payload: "noFilesSelected" });
+ return;
+ }
- if (!state.selectedFiles.length) {
- dispatch({ type: "SET_ERROR", payload: "noFilesSelected" });
- return;
- }
+ const fileExtension = state.selectedFiles[0].name
+ .split(".")
+ .pop()
+ ?.toLowerCase();
+ dispatch({ type: "SET_IS_UPLOADING", payload: true });
+ resetProgress();
- const fileExtension = state.selectedFiles[0].name
- .split(".")
- .pop()
- .toLowerCase();
- dispatch({ type: "SET_IS_UPLOADING", payload: true });
- resetProgress();
+ const config = {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ },
+ onUploadProgress: (progressEvent) => {
+ const percentComplete = Math.round(
+ (progressEvent.loaded * 100) / progressEvent.total,
+ );
+ setProgress(percentComplete);
+ },
+ };
- const formData = new FormData();
- formData.append("file", state.selectedFiles[0]);
- let endpoint: "add_datasource" | "upload" | null = null;
+ try {
+ let response;
+ if (fileExtension === "tiff") {
+ const formData = new FormData();
+ formData.append("file", state.selectedFiles[0]);
+ formData.append("tiffMetadata", JSON.stringify(state.tiffMetadata));
+ formData.append("datasourceName", datasourceName);
- if (fileExtension === "csv") {
+ response = await axios.post(
+ `${root}/add_or_update_image_datasource`,
+ formData,
+ config,
+ );
+ } else {
+ const formData = new FormData();
+ formData.append("file", state.selectedFiles[0]);
formData.append("name", datasourceName);
- // formData.append("view", "TRUE");
- // formData.append("backend", "TRUE");
formData.append("replace", "");
- endpoint = "add_datasource";
- } else if (fileExtension === "tiff") {
- formData.append("project_name", projectName);
- // endpoint = "upload"
- }
- const config = {
- headers: {
- "Content-Type": "multipart/form-data",
- },
- onUploadProgress: (progressEvent) => {
- const percentComplete = Math.round(
- (progressEvent.loaded * 100) / progressEvent.total,
- );
- setProgress(percentComplete); // Update the progress as the file uploads
- },
- };
-
- try {
- const response = await axios.post(
+ response = await axios.post(
`${root}/add_datasource`,
formData,
config,
);
- console.log("File uploaded successfully", response.data);
-
- if (response.status === 200) {
- dispatch({ type: "SET_IS_UPLOADING", payload: false });
- dispatch({ type: "SET_SUCCESS", payload: true });
-
- // Perform second request if the file is TIFF
- if (fileExtension === "tiff") {
- try {
- const metadataResponse = await axios.post(
- `${root}/add_or_update_image_datasource`,
- {
- tiffMetadata: state.tiffMetadata,
- datasourceName: datasourceName,
- },
- );
- console.log(
- "Metadata updated successfully",
- metadataResponse.data,
- );
- chartManager.saveState();
- } catch (metadataError) {
- console.error(
- "Error updating metadata:",
- metadataError,
- );
- dispatch({
- type: "SET_ERROR",
- payload: {
- message: "Failed to update metadata.",
- traceback: metadataError.message,
- },
- });
- }
- }
- } else {
- console.error(
- `Failed to confirm: Server responded with status ${response.status}`,
- );
- dispatch({ type: "SET_IS_UPLOADING", payload: false });
- dispatch({
- type: "SET_ERROR",
- payload: {
- message: `Confirmation failed with status: ${response.status}`,
- status: response.status,
- traceback: "Server responded with non-200 status",
- },
- });
+ }
+
+ console.log("File uploaded successfully", response.data);
+ if (response.status === 200) {
+ dispatch({ type: "SET_IS_UPLOADING", payload: false });
+ dispatch({ type: "SET_SUCCESS", payload: true });
+
+ if (fileExtension === "tiff") {
+ chartManager.saveState();
}
- } catch (error) {
- console.error("Error uploading file:", error);
+ } else {
+ console.error(
+ `Failed to confirm: Server responded with status ${response.status}`,
+ );
dispatch({ type: "SET_IS_UPLOADING", payload: false });
dispatch({
type: "SET_ERROR",
payload: {
- message: "Upload failed due to a network error.",
- traceback: error.message,
+ message: `Confirmation failed with status: ${response.status}`,
+ status: response.status,
+ traceback: "Server responded with non-200 status",
},
});
}
- };
-
- const handleClose = async () => {
- dispatch({ type: "SET_FILE_SUMMARY", payload: null });
- onResize(450, 320);
- onClose();
- };
-
- return (
-
- {state.isUploading ? (
-
-
- {"Your file is being uploaded, please wait..."}
-
-
-
- ) : state.isInserting ? (
-
-
- {"Your file is being processed, please wait..."}
-
-
-
- ) : state.success ? (
- <>
-
- Success!
-
- The file was uploaded successfully to the database.
-
-
-
- >
- ) : state.error ? (
- <>
-
-
- An error occurred while uploading the file:
-
- {state.error.message}
- {state.error.traceback && (
- {state.error.traceback}
- )}
-
- >
- ) : state.isValidating ? (
-
- {"Validating data, please wait..."}
-
-
- ) : state.validationResult ? (
- <>
- {state.fileType === "csv" && (
- <>
-
-
- {"Uploaded File Summary"}
-
-
-
-
-
- {"File name"}{" "}
- {csvSummary.fileName}
-
-
- {"Number of rows"}{" "}
- {csvSummary.rowCount}
-
-
- {"Number of columns"}{" "}
- {csvSummary.columnCount}
-
-
- {"File size"}{" "}
- {csvSummary.fileSize} MB
-
-
-
-
-
-
-
-
- >
- )}
-
- {state.fileType === "tiff" && state.tiffMetadata && (
- <>
-
-
-
-
-
-
- {"Uploaded File Summary"}
-
-
-
- {"File name:"}
- {" "}
- {tiffSummary.fileName}
-
-
-
- {"File size:"}
- {" "}
- {tiffSummary.fileSize} MB
-
-
-
-
-
-
-
-
- Image Preview:
-
-
-
-
-
-
-
- {showMetadata ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
-
-
- >
- )}
- >
+ } catch (error) {
+ console.error("Error uploading file:", error);
+ dispatch({ type: "SET_IS_UPLOADING", payload: false });
+ dispatch({
+ type: "SET_ERROR",
+ payload: {
+ message: "Upload failed due to a network error.",
+ traceback: error.message,
+ },
+ });
+ }
+ };
+
+ const handleClose = async () => {
+ dispatch({ type: "SET_FILE_SUMMARY", payload: null });
+ onResize(450, 320);
+ onClose();
+ };
+
+ return (
+
+ {state.isUploading ? (
+
+ {"Your file is being uploaded, please wait..."}
+
+
+ ) : state.isInserting ? (
+
+ {"Your file is being processed, please wait..."}
+
+
+ ) : state.success ? (
+ <>
+
+ Success!
+
+ The file was uploaded successfully to the database.
+
+
+
+ >
+
+ ) : state.error ? (
+ <>
+
+ An error occurred while uploading the file:
+ {state.error.message}
+ {state.error.traceback && (
+ {state.error.traceback}
+ )}
+
+ >
+ ) : state.isValidating ? (
+
+ {"Validating data, please wait..."}
+
+
+ ) : state.validationResult ? (
+ <>
+ {state.fileType === "csv" && (
+ <>
+
+ {"Uploaded File Summary"}
+
+ {"Datasource name"} {csvSummary.datasourceName}
+
+
+ {"File name"} {csvSummary.fileName}
+
+
+ {"Number of rows"} {csvSummary.rowCount}
+
+
+ {"Number of columns"} {csvSummary.columnCount}
+
+
+ {"File size"} {csvSummary.fileSize} MB
+
+
+
+
+
+
+
+ >
+ )}
+
+
+ {state.fileType === "tiff" && state.tiffMetadata && (
+ <>
+
+
+
+
+
+ {"Uploaded File Summary"}
+
+ {"File name:"} {tiffSummary.fileName}
+
+
+ {"File size:"} {tiffSummary.fileSize} MB
+
+
+
+
+
+
+
+
+
+ Image Preview:
+
+
+
+
+
+
+
+ {showMetadata ? : }
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+ >
+ ) : (
+ <>
+
+
+ {isDragActive ? (
+
) : (
- <>
-
-
-
-
- {isDragActive ? (
-
- ) : (
- 0
- ? `Selected file: ${state.selectedFiles[0].name}`
- : rejectionMessage
- }
- className={`${rejectionMessageStyle} text-sm`}
- />
- )}
-
- {"Choose File"}
-
-
-
- >
+ 0 ? `Selected file: ${state.selectedFiles[0].name}` : rejectionMessage} className={rejectionMessageStyle} />
)}
-
- );
+ {"Choose File"}
+
+
+
+
+
+ >
+ )}
+
+ );
};
-export default observer(FileUploadDialogComponent);
+export default observer(FileUploadDialogComponent);;
\ No newline at end of file