From 55a9c31990bc3c0fa72c79ad5b4e096af11b8d5d Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:07:48 -0700 Subject: [PATCH 01/13] Initial options for packages 3.12 --- environment.yml | 44 ++++++++++++++++++++++---------------------- setup.py | 18 +++++++++--------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/environment.yml b/environment.yml index 8e66d9d..d117e67 100644 --- a/environment.yml +++ b/environment.yml @@ -3,27 +3,27 @@ channels: - conda-forge - defaults dependencies: -- pip=22.3.1 -- python=3.11.0 +- pip=24.1.2 +- python=3.12.0 - pip: - - astroid==2.12.13 - - attrs==22.1.0 - - coverage==6.5.0 + - astroid>=3.2.3 + - attrs>=23.2.0 + - coverage>=7.6.0 - jupyter==1.0.0 - - matplotlib==3.6.2 - - mock==4.0.3 - - numpy==1.23.5 - - pandas==1.2.3 - - pillow==9.3.0 - - pylint==2.15.6 - - pytest==7.2.0 - - python-dotenv==0.21.0 - - requests==2.28.1 - - scikit-image==0.19.3 - - scikit-learn==1.1.3 - - setuptools==65.5.1 - - sphinx-argparse==0.2.2 - - sphinx==5.3.0 - - tifffile==2022.10.10 - - tox==3.27.1 - - tqdm==4.64.1 + - matplotlib>=3.9.0 + - mock>=5.1.0 + - numpy<2.0.0 + - pandas>=2.2.0 + - pillow>=10.4.0 + - pylint>=3.2.0 + - pytest>=8.2.0 + - python-dotenv>=1.0.0 + - requests>=2.32.0 + - scikit-image>=0.24.0 + - scikit-learn>=1.5.0 + - setuptools>=70.3.0 + - sphinx-argparse>=0.4.0 + - sphinx>=7.4.0 + - tifffile<2024.6.18 + - tox>=4.16.0 + - tqdm>=4.66.0 diff --git a/setup.py b/setup.py index 0ce649b..20ed708 100644 --- a/setup.py +++ b/setup.py @@ -11,17 +11,17 @@ url='https://github.com/ionpath/mibilib', description='Python utilities for IONpath MIBItracker and MIBItiff data', license='GNU General Public License v3.0', - python_requires='~=3.11.0', + python_requires='~=3.12.0', install_requires=[ - 'matplotlib==3.6.2', - 'numpy==1.23.5', - 'pandas==1.2.3', - 'pillow==9.3.0', + 'matplotlib>=3.9.0', + 'numpy<2.0.0', + 'pandas>=2.2.0', + 'pillow>=10.4.0', 'requests>=2.28.1', - 'scikit-image==0.19.3', - 'scikit-learn==1.1.3', - 'tifffile==2022.10.10', - 'tqdm==4.64.1', + 'scikit-image>=0.24.0', + 'scikit-learn>=1.5.0', + 'tifffile<2024.6.18', + 'tqdm>=4.66.0', ], packages=['mibitracker', 'mibidata'] ) From ed75c46c9aa3f64bbfe358114aa80b28e7d92d7d Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Fri, 19 Jul 2024 08:57:52 -0700 Subject: [PATCH 02/13] Completed code updates, pushing for testing and pull request --- mibidata/tests/test_segmentation.py | 8 ++++---- mibitracker/request_helpers.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mibidata/tests/test_segmentation.py b/mibidata/tests/test_segmentation.py index b3eb829..b9d21f0 100644 --- a/mibidata/tests/test_segmentation.py +++ b/mibidata/tests/test_segmentation.py @@ -187,9 +187,9 @@ def test_extract_cell_dataframe(self): second_total = [1, 3] # Check coords and areas only expected_from_labels = pd.DataFrame( - np.array([areas, x_centroids, y_centroids]).T, + np.array([areas, x_centroids, y_centroids]).T.astype(np.int64), columns=['area', 'x_centroid', 'y_centroid'], - index=pd.Index(labels, name='label')) + index=pd.Index(labels, name='label', dtype=np.int32)) pdt.assert_frame_equal( segmentation.extract_cell_dataframe(cell_labels), expected_from_labels) @@ -272,13 +272,13 @@ def test_filter_by_size(self): [1, 1, 3, 3], [4, 4, 3, 3], [0, 4, 3, 3] - ]) + ], dtype=np.int64) expected = np.array([ [0, 1, 1, 0], [1, 1, 0, 0], [2, 2, 0, 0], [0, 2, 0, 0] - ]) + ], dtype=np.int64) df = segmentation.extract_cell_dataframe(expected) filtered_image, filtered_df = segmentation.filter_by_size( cell_labels, 3, 5) diff --git a/mibitracker/request_helpers.py b/mibitracker/request_helpers.py index 7c887e4..7b37075 100644 --- a/mibitracker/request_helpers.py +++ b/mibitracker/request_helpers.py @@ -104,7 +104,7 @@ def __init__(self, 'Provide either both an email and password or a token' ) - retry = Retry(status=retries, method_whitelist=retry_methods, + retry = Retry(status=retries, allowed_methods=retry_methods, status_forcelist=retry_codes, backoff_factor=0.3) # Set this session to use these retry settings for all http[s] requests self.session.mount('http://', HTTPAdapter(max_retries=retry)) From 72f611c9425472a3000200e72a7534c30cef1a89 Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:31:06 -0700 Subject: [PATCH 03/13] Quick Fix to Pip version --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index d117e67..1df8424 100644 --- a/environment.yml +++ b/environment.yml @@ -3,7 +3,7 @@ channels: - conda-forge - defaults dependencies: -- pip=24.1.2 +- pip=24.0.0 - python=3.12.0 - pip: - astroid>=3.2.3 From a26375306a77d20a883936ae2467f2ccded5579a Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:15:45 -0700 Subject: [PATCH 04/13] Testing if removing specific pytest version fixes the issue --- environment.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/environment.yml b/environment.yml index 1df8424..3e981cc 100644 --- a/environment.yml +++ b/environment.yml @@ -16,7 +16,6 @@ dependencies: - pandas>=2.2.0 - pillow>=10.4.0 - pylint>=3.2.0 - - pytest>=8.2.0 - python-dotenv>=1.0.0 - requests>=2.32.0 - scikit-image>=0.24.0 From 3257793e396218f188e11d749407783e30a5ae30 Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:45:43 -0700 Subject: [PATCH 05/13] Updated pytest version --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 3e981cc..d5c9bae 100644 --- a/environment.yml +++ b/environment.yml @@ -16,6 +16,7 @@ dependencies: - pandas>=2.2.0 - pillow>=10.4.0 - pylint>=3.2.0 + - pytest>=8.3.0 - python-dotenv>=1.0.0 - requests>=2.32.0 - scikit-image>=0.24.0 From 4ecf0979b74f47cc5728bd1a37277a5f236a9900 Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:11:34 -0700 Subject: [PATCH 06/13] Testing if adding py fixes issue --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index d5c9bae..637ca64 100644 --- a/environment.yml +++ b/environment.yml @@ -16,6 +16,7 @@ dependencies: - pandas>=2.2.0 - pillow>=10.4.0 - pylint>=3.2.0 + - py>=1.11.0 - pytest>=8.3.0 - python-dotenv>=1.0.0 - requests>=2.32.0 From 7731d9f55fea599c3c0dfa7c4038740126a5f616 Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:27:40 -0700 Subject: [PATCH 07/13] Issue with index being the wrong dtype --- mibidata/tests/test_segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mibidata/tests/test_segmentation.py b/mibidata/tests/test_segmentation.py index b9d21f0..b029487 100644 --- a/mibidata/tests/test_segmentation.py +++ b/mibidata/tests/test_segmentation.py @@ -189,7 +189,7 @@ def test_extract_cell_dataframe(self): expected_from_labels = pd.DataFrame( np.array([areas, x_centroids, y_centroids]).T.astype(np.int64), columns=['area', 'x_centroid', 'y_centroid'], - index=pd.Index(labels, name='label', dtype=np.int32)) + index=pd.Index(labels, name='label', dtype=np.int64)) pdt.assert_frame_equal( segmentation.extract_cell_dataframe(cell_labels), expected_from_labels) From c2345282625b4f6c07c294f04e86f1c44f21e05f Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:45:59 -0700 Subject: [PATCH 08/13] Updated .pylintrc exceptions --- .pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index c595f99..f9ee7b5 100644 --- a/.pylintrc +++ b/.pylintrc @@ -387,4 +387,5 @@ max-locals=50 # Exceptions that will emit a warning when being caught. Defaults to # "Exception" -overgeneral-exceptions=Exception \ No newline at end of file +overgeneral-exceptions=builtins.BaseException, + bultins.Exception \ No newline at end of file From cca52cb70188840cbc9a5ea2b11862dc5d41a5be Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Tue, 23 Jul 2024 10:26:33 -0700 Subject: [PATCH 09/13] Fixed Pylint errors for request_helpers --- mibitracker/request_helpers.py | 80 ++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/mibitracker/request_helpers.py b/mibitracker/request_helpers.py index 7b37075..c16a8fc 100644 --- a/mibitracker/request_helpers.py +++ b/mibitracker/request_helpers.py @@ -1,7 +1,6 @@ """Helper class for making and retrying requests to the MIBItracker. Copyright (C) 2021 Ionpath, Inc. All rights reserved.""" - import datetime import io import json @@ -94,7 +93,7 @@ def __init__(self, # The token will be used in lieu of email and password if provided if token is not None: self.session.headers.update({ - 'Authorization': 'JWT {}'.format(token) + 'Authorization': f'JWT {token}' }) self.session.options(self.url) elif email is not None and password is not None: @@ -113,24 +112,24 @@ def __init__(self, def _auth(self, url, email, password): """Adds an authorization token to the session's default header.""" response = self.session.post( - '{}/api-token-auth/'.format(url), + f'{url}/api-token-auth/', headers={'content-type': 'application/json'}, data=json.dumps({'email': email, 'password': password})) token = response.json()['token'] - self.session.headers.update({'Authorization': 'JWT {}'.format(token)}) + self.session.headers.update({'Authorization': f'JWT {token}'}) def refresh(self): """Refreshes the authorization token stored in the session header. Raises HTTP 400 if attempting to refresh an expired token.""" token = self.session.post( # use the session to avoid recursion - '{}/api-token-refresh/'.format(self.url), + f'{self.url}/api-token-refresh/', data=json.dumps( {'token': self.session.headers['Authorization'][4:]} ), headers={'content-type': 'application/json'}, ).json()['token'] - self.session.headers.update({'Authorization': 'JWT {}'.format(token)}) + self.session.headers.update({'Authorization': f'JWT {token}'}) def _check_refresh(self): current_time = datetime.datetime.now() @@ -143,7 +142,7 @@ def _check_refresh(self): @staticmethod def _prepare_route(route): if not route.startswith('/'): - return '/{}'.format(route) + return '/{route}' return route def get(self, route, *args, **kwargs): @@ -159,8 +158,8 @@ def get(self, route, *args, **kwargs): The response from ``requests.Session.get``. """ self._check_refresh() - return self.session.get('{}{}'.format( - self.url, self._prepare_route(route)), *args, **kwargs) + return self.session.get(f'{self.url}{self._prepare_route(route)}', + *args, **kwargs) def post(self, route, *args, **kwargs): """Makes a POST request to the url using the session. @@ -174,8 +173,8 @@ def post(self, route, *args, **kwargs): The response from ``requests.Session.post``. """ self._check_refresh() - return self.session.post('{}{}'.format( - self.url, self._prepare_route(route)), *args, **kwargs) + return self.session.post(f'{self.url}{self._prepare_route(route)}', + *args, **kwargs) def put(self, route, *args, **kwargs): """Makes a PUT request to the url using the session. @@ -189,8 +188,8 @@ def put(self, route, *args, **kwargs): The response from ``requests.Session.put``. """ self._check_refresh() - return self.session.put('{}{}'.format( - self.url, self._prepare_route(route)), *args, **kwargs) + return self.session.put(f'{self.url}{self._prepare_route(route)}', + *args, **kwargs) def delete(self, route, *args, **kwargs): """Makes a DELETE request to the url using the session. @@ -204,8 +203,8 @@ def delete(self, route, *args, **kwargs): The response from ``requests.Session.delete``. """ self._check_refresh() - return self.session.delete('{}{}'.format( - self.url, self._prepare_route(route)), *args, **kwargs) + return self.session.delete(f'{self.url}{self._prepare_route(route)}', + *args, **kwargs) def download_file(self, path): """Downloads a file from MIBItracker storage. @@ -264,13 +263,14 @@ def copy_run(self, old_label, new_label, **kwargs): """ # We don't expect there to be many runs with the same label, so it is # safe to turn paging off. - response = self.get('/runs/?label={}&paging=no'.format(old_label)) + response = self.get(f'/runs/?label={old_label}&paging=no') data = response.json() try: assert len(data) == 1 - except AssertionError: - raise MibiTrackerError('Expected 1 run with label {}, but {} were ' - 'found'.format(old_label, len(data))) + except AssertionError as err: + raise MibiTrackerError(f'Expected 1 run with label {old_label},' + f' but {len(data)} were found')\ + from err data = data[0] # Get the XMLs from the original runs. They may not have the date and @@ -288,7 +288,7 @@ def copy_run(self, old_label, new_label, **kwargs): 'project': data['project'] and data['project']['id'], # optional 'description': data['description'], 'operator': data['operator'], # Not yet used, but field exists - 'user_run_date': '{}T00:00:00'.format(data['run_date']), + 'user_run_date': f'{data['run_date']}T00:00:00', } # The old run date supercedes the possibly-missing date in the xml # A timestamp is temporarily added for the JSON encoded but will be @@ -320,15 +320,15 @@ def copy_run_image_metadata(self, old_run_label, new_run_label, **kwargs): response JSON returned when updating the new images. """ old_images = self.get( - '/images/?run__label={}&paging=no'.format(old_run_label)) + f'/images/?run__label={old_run_label}&paging=no') image_map = {} for item in old_images.json(): # Get image from copied run response = self.get( - '/images/?run__label={}&folder={}&paging=no'.format( - new_run_label, item['folder'])) + f'/images/?run__label={new_run_label}&folder={item['folder']}' + '&paging=no') assert len(response.json()) == 1 new_image = response.json()[0] # Update the section and tissue of the copied image using @@ -371,6 +371,7 @@ def copy_run_image_metadata(self, old_run_label, new_run_label, **kwargs): content_type = 'image/png' buf = self.download_file(sed_path) + # pylint: disable=possibly-used-before-assignment files = {'attachment': (sed, buf, content_type)} data = updated_image else: @@ -381,7 +382,7 @@ def copy_run_image_metadata(self, old_run_label, new_run_label, **kwargs): headers.update({'content-type': 'application/json'}) response = self.put( - '/images/{}/'.format(new_image['id']), + f'/images/{new_image['id']}/', files=files, data=data, headers=headers @@ -429,11 +430,12 @@ def upload_mibitiff(self, tiff_file, run_id=None): try: with open(tiff_file, 'rb') as fh: self._upload_mibitiff(response['url'], fh) - except TypeError: + except TypeError as err: try: tiff_file.seek(0) except: - raise TypeError('tiff_file must be a string or file object') + raise TypeError('tiff_file must be a string' + 'or file object') from err self._upload_mibitiff(response['url'], tiff_file) return self.post( '/upload_mibitiff/', @@ -447,7 +449,7 @@ def _upload_channel(self, image_id, image_file, filename): files = { 'attachment': (filename, image_file, - 'image/{}'.format(ext[1:].lower())) + f'image/{ext[1:].lower()}') } response = self.post( @@ -480,17 +482,19 @@ def upload_channel(self, image_id, image_file, filename=None): if not filename: filename = os.path.basename(image_file) return self._upload_channel(image_id, fh, filename) - except TypeError: + except TypeError as err: try: image_file.seek(0) except: - raise TypeError('image_file must be a string or file object') + raise TypeError('image_file must be a ' + 'string or file object') from err if not filename: try: filename = os.path.basename(image_file.name) - except AttributeError: + except AttributeError as aErr: raise ValueError('filename must be provided with a file ' - 'object that does not have a name') + 'object that does not have a name' + ) from aErr return self._upload_channel(image_id, image_file, filename) def run_images(self, run_label): @@ -518,7 +522,7 @@ def image_conjugates(self, image_id): section assigned, or if its section does not have a panel assigned. """ return self.get( - '/images/{}/conjugates/'.format(image_id), + f'/images/{image_id}/conjugates/', params={'paging': 'no'}).json() def image_id(self, run_label, fov_id): @@ -580,7 +584,7 @@ def get_mibi_image(self, image_id): Return: A MibiImage instance of the requested image. """ - image_info = self.get('images/{}/'.format(image_id)).json() + image_info = self.get(f'images/{image_id}/') tiff_path = '/'.join((image_info['run']['path'], image_info['folder'], 'summed_image.tiff')) tiff_data = self.download_file(tiff_path) @@ -605,10 +609,11 @@ def get_channel_data(self, image_id, channel_name): response.raise_for_status() except HTTPError as e: if e.response.status_code == 404: - raise MibiTrackerError( - f'Channel \'{channel_name}\' not found in the image.') + raise MibiTrackerError(f'Channel \'{channel_name}\' ' + 'not found in the image.') from e raise e + # pylint: disable=missing-timeout png = requests.get(response.json()['url']) buf = io.BytesIO() buf.write(png.content) @@ -647,7 +652,7 @@ class StatusCheckedSession(requests.Session): """Raises for HTTP errors and adds any response JSON to the message.""" def __init__(self, timeout=SESSION_TIMEOUT): - super(StatusCheckedSession, self).__init__() + super().__init__() self.timeout = timeout @staticmethod @@ -659,7 +664,8 @@ def _check_status(response): response_json = response.json() except json.decoder.JSONDecodeError: response_json = None - raise HTTPError(str(e), response_json, response=response) + raise HTTPError(str(e), response_json, + response=response) from e return response def _set_timeout(self, kwargs): From 1b9961fffff846404415343e52b3d631685f3c92 Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:20:37 -0700 Subject: [PATCH 10/13] Missed f for f string --- mibitracker/request_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mibitracker/request_helpers.py b/mibitracker/request_helpers.py index c16a8fc..8bfb928 100644 --- a/mibitracker/request_helpers.py +++ b/mibitracker/request_helpers.py @@ -142,7 +142,7 @@ def _check_refresh(self): @staticmethod def _prepare_route(route): if not route.startswith('/'): - return '/{route}' + return f'/{route}' return route def get(self, route, *args, **kwargs): From 388b14d1d70a574efa979fff0059b5b122cfe0a6 Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:25:17 -0700 Subject: [PATCH 11/13] Undid removing .json() --- mibitracker/request_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mibitracker/request_helpers.py b/mibitracker/request_helpers.py index 8bfb928..95dad33 100644 --- a/mibitracker/request_helpers.py +++ b/mibitracker/request_helpers.py @@ -584,7 +584,7 @@ def get_mibi_image(self, image_id): Return: A MibiImage instance of the requested image. """ - image_info = self.get(f'images/{image_id}/') + image_info = self.get(f'images/{image_id}/').json() tiff_path = '/'.join((image_info['run']['path'], image_info['folder'], 'summed_image.tiff')) tiff_data = self.download_file(tiff_path) From 774c7014d4da5a6f5400619c67d7cd68876abf0a Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Fri, 26 Jul 2024 10:28:41 -0700 Subject: [PATCH 12/13] Made version works for 3.9 upwards --- mibitracker/request_helpers.py | 6 +++--- setup.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mibitracker/request_helpers.py b/mibitracker/request_helpers.py index 95dad33..f88f5eb 100644 --- a/mibitracker/request_helpers.py +++ b/mibitracker/request_helpers.py @@ -288,7 +288,7 @@ def copy_run(self, old_label, new_label, **kwargs): 'project': data['project'] and data['project']['id'], # optional 'description': data['description'], 'operator': data['operator'], # Not yet used, but field exists - 'user_run_date': f'{data['run_date']}T00:00:00', + 'user_run_date': f"{data['run_date']}T00:00:00", } # The old run date supercedes the possibly-missing date in the xml # A timestamp is temporarily added for the JSON encoded but will be @@ -327,7 +327,7 @@ def copy_run_image_metadata(self, old_run_label, new_run_label, **kwargs): # Get image from copied run response = self.get( - f'/images/?run__label={new_run_label}&folder={item['folder']}' + f"/images/?run__label={new_run_label}&folder={item['folder']}" '&paging=no') assert len(response.json()) == 1 new_image = response.json()[0] @@ -382,7 +382,7 @@ def copy_run_image_metadata(self, old_run_label, new_run_label, **kwargs): headers.update({'content-type': 'application/json'}) response = self.put( - f'/images/{new_image['id']}/', + f"/images/{new_image['id']}/", files=files, data=data, headers=headers diff --git a/setup.py b/setup.py index 20ed708..bdf7595 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ url='https://github.com/ionpath/mibilib', description='Python utilities for IONpath MIBItracker and MIBItiff data', license='GNU General Public License v3.0', - python_requires='~=3.12.0', + python_requires='>=3.9', install_requires=[ 'matplotlib>=3.9.0', 'numpy<2.0.0', From aaed314b5fa52a495c97cc0d566118d9869bd1e3 Mon Sep 17 00:00:00 2001 From: DeeAich <114917172+DeeeAich@users.noreply.github.com> Date: Tue, 15 Oct 2024 06:14:57 -0700 Subject: [PATCH 13/13] Fixed to work with 3.8 --- setup.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index bdf7595..e71db46 100644 --- a/setup.py +++ b/setup.py @@ -11,17 +11,17 @@ url='https://github.com/ionpath/mibilib', description='Python utilities for IONpath MIBItracker and MIBItiff data', license='GNU General Public License v3.0', - python_requires='>=3.9', + python_requires='>=3.8', install_requires=[ - 'matplotlib>=3.9.0', - 'numpy<2.0.0', - 'pandas>=2.2.0', - 'pillow>=10.4.0', - 'requests>=2.28.1', - 'scikit-image>=0.24.0', - 'scikit-learn>=1.5.0', + 'matplotlib>=3.6', + 'numpy<2.0', + 'pandas>=1.0', + 'pillow>=9.0', + 'requests>=2.28', + 'scikit-image>=0.19', + 'scikit-learn>=1.1', 'tifffile<2024.6.18', - 'tqdm>=4.66.0', + 'tqdm>=4.64', ], packages=['mibitracker', 'mibidata'] )